@naulon/shared 0.0.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/dist/attribution.d.ts +92 -0
- package/dist/attribution.d.ts.map +1 -0
- package/dist/attribution.js +145 -0
- package/dist/attribution.js.map +1 -0
- package/dist/botAuthSign.d.ts +74 -0
- package/dist/botAuthSign.d.ts.map +1 -0
- package/dist/botAuthSign.js +90 -0
- package/dist/botAuthSign.js.map +1 -0
- package/dist/config.d.ts +131 -0
- package/dist/config.d.ts.map +1 -0
- package/dist/config.js +400 -0
- package/dist/config.js.map +1 -0
- package/dist/credits.d.ts +7 -0
- package/dist/credits.d.ts.map +1 -0
- package/dist/credits.js +7 -0
- package/dist/credits.js.map +1 -0
- package/dist/eip3009.d.ts +69 -0
- package/dist/eip3009.d.ts.map +1 -0
- package/dist/eip3009.js +34 -0
- package/dist/eip3009.js.map +1 -0
- package/dist/eventsink.d.ts +19 -0
- package/dist/eventsink.d.ts.map +1 -0
- package/dist/eventsink.js +110 -0
- package/dist/eventsink.js.map +1 -0
- package/dist/index.d.ts +14 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +14 -0
- package/dist/index.js.map +1 -0
- package/dist/license.d.ts +178 -0
- package/dist/license.d.ts.map +1 -0
- package/dist/license.js +252 -0
- package/dist/license.js.map +1 -0
- package/dist/networks.d.ts +63 -0
- package/dist/networks.d.ts.map +1 -0
- package/dist/networks.js +84 -0
- package/dist/networks.js.map +1 -0
- package/dist/observationsink.d.ts +22 -0
- package/dist/observationsink.d.ts.map +1 -0
- package/dist/observationsink.js +83 -0
- package/dist/observationsink.js.map +1 -0
- package/dist/publisher.d.ts +209 -0
- package/dist/publisher.d.ts.map +1 -0
- package/dist/publisher.js +2 -0
- package/dist/publisher.js.map +1 -0
- package/dist/settlementEmit.d.ts +31 -0
- package/dist/settlementEmit.d.ts.map +1 -0
- package/dist/settlementEmit.js +65 -0
- package/dist/settlementEmit.js.map +1 -0
- package/dist/supabase.d.ts +8 -0
- package/dist/supabase.d.ts.map +1 -0
- package/dist/supabase.js +47 -0
- package/dist/supabase.js.map +1 -0
- package/dist/types.d.ts +154 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +22 -0
- package/dist/types.js.map +1 -0
- package/package.json +44 -0
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
/** The chains this gate can settle on — each a SupportedChainName in the SDK. */
|
|
2
|
+
export type NetworkName = "arcTestnet" | "baseSepolia" | "base";
|
|
3
|
+
export interface SettlementNetwork {
|
|
4
|
+
/** GatewayClient `chain` key — must match a SupportedChainName in the SDK. */
|
|
5
|
+
chainName: NetworkName;
|
|
6
|
+
/** x402 network id. MUST be `eip155:<chainId>` — the SDK's BatchEvmScheme
|
|
7
|
+
* rejects any other format (verify compares against `eip155:${chain.id}`). */
|
|
8
|
+
network: string;
|
|
9
|
+
chainId: number;
|
|
10
|
+
/** Native USDC token (ERC-20). */
|
|
11
|
+
usdc: `0x${string}`;
|
|
12
|
+
/** USDC EIP-712 domain `name`/`version` for RAW EIP-3009 signing (the memo
|
|
13
|
+
* self-relay rail). VERIFIED per-network, never assumed: Arc testnet's USDC names
|
|
14
|
+
* itself "USDC"/"2" on-chain (NOT the mainnet FiatToken "USD Coin") — a mismatch
|
|
15
|
+
* silently rejects every signature (recovers signer != from). Only the memo rail
|
|
16
|
+
* reads these (the Gateway path signs the GatewayWallet domain via `gatewayExtra`),
|
|
17
|
+
* so they are optional and set only where a memo settle runs. The `USDC_EIP712_NAME`
|
|
18
|
+
* env var overrides `name` for a deploy that hits an unexpected on-chain value. */
|
|
19
|
+
usdcName?: string;
|
|
20
|
+
usdcVersion?: string;
|
|
21
|
+
/** GatewayWallet contract — the x402 `extra.verifyingContract`. Differs between
|
|
22
|
+
* Circle's testnet and mainnet deployments, so it lives per-network. */
|
|
23
|
+
gatewayWallet: `0x${string}`;
|
|
24
|
+
/** Circle Gateway facilitator endpoint. The SDK 3.x defaults to mainnet, so a
|
|
25
|
+
* testnet network must point the seller-side BatchFacilitatorClient here. */
|
|
26
|
+
gatewayApiUrl: string;
|
|
27
|
+
rpcUrl: string;
|
|
28
|
+
/** false only for a real-money mainnet — the guard behind the safe default. */
|
|
29
|
+
testnet: boolean;
|
|
30
|
+
/** Arc-only transaction-extension capability. Present ONLY on chains that ship the
|
|
31
|
+
* Memo predeploy (Arc); ABSENT on Base / Base Sepolia (they have no equivalent).
|
|
32
|
+
* The settle path gates memo emission on the PRESENCE of this field, NEVER on
|
|
33
|
+
* chainName — so a chain swap to Base omits memos automatically (illegal state
|
|
34
|
+
* unrepresentable). The memo wraps the buyer→author transfer via a self-relay,
|
|
35
|
+
* which stays custody-free (the relayer pays gas, never touches the USDC). */
|
|
36
|
+
memo?: {
|
|
37
|
+
/** The Memo predeploy. `memo(target,data,memoId,memoData)` executes the subcall
|
|
38
|
+
* through the CALL_FROM precompile (preserving the relayer as caller) AND emits
|
|
39
|
+
* an indexed `Memo(sender,target,callDataHash,memoId,memo,memoIndex)` event.
|
|
40
|
+
* Verified on testnet.arcscan.app (Blockscout). Arc-testnet-only. */
|
|
41
|
+
contract: `0x${string}`;
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
/** Field-presence capability gate — true iff this network ships the Memo predeploy.
|
|
45
|
+
* The settle path narrows on THIS, never on `chainName`, so adding a memo-capable
|
|
46
|
+
* chain (or swapping to a memo-less one) needs no settle-path edit. */
|
|
47
|
+
export declare function supportsMemo(net: SettlementNetwork): net is SettlementNetwork & {
|
|
48
|
+
memo: NonNullable<SettlementNetwork["memo"]>;
|
|
49
|
+
};
|
|
50
|
+
export declare const NETWORKS: Record<NetworkName, SettlementNetwork>;
|
|
51
|
+
/** Back-compat alias for the original single-network export + interface. */
|
|
52
|
+
export declare const ARC_TESTNET: SettlementNetwork;
|
|
53
|
+
/** @deprecated use {@link SettlementNetwork}. */
|
|
54
|
+
export type ArcNetwork = SettlementNetwork;
|
|
55
|
+
/** Look up a network by name. */
|
|
56
|
+
export declare function getNetwork(name: NetworkName): SettlementNetwork;
|
|
57
|
+
/** The network the gate is configured to settle on (`SETTLEMENT_NETWORK`). */
|
|
58
|
+
export declare function activeNetwork(): SettlementNetwork;
|
|
59
|
+
/** The Gateway batching x402 `extra` block, naming the verifying contract. */
|
|
60
|
+
export declare function gatewayExtra(net?: SettlementNetwork): Record<string, unknown>;
|
|
61
|
+
/** Convert a decimal USDC string/number to atomic units (6 decimals). */
|
|
62
|
+
export declare function toAtomicUsdc(amount: number | string): string;
|
|
63
|
+
//# sourceMappingURL=networks.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"networks.d.ts","sourceRoot":"","sources":["../src/networks.ts"],"names":[],"mappings":"AAiBA,iFAAiF;AACjF,MAAM,MAAM,WAAW,GAAG,YAAY,GAAG,aAAa,GAAG,MAAM,CAAC;AAEhE,MAAM,WAAW,iBAAiB;IAChC,8EAA8E;IAC9E,SAAS,EAAE,WAAW,CAAC;IACvB;mFAC+E;IAC/E,OAAO,EAAE,MAAM,CAAC;IAChB,OAAO,EAAE,MAAM,CAAC;IAChB,kCAAkC;IAClC,IAAI,EAAE,KAAK,MAAM,EAAE,CAAC;IACpB;;;;;;wFAMoF;IACpF,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB;6EACyE;IACzE,aAAa,EAAE,KAAK,MAAM,EAAE,CAAC;IAC7B;kFAC8E;IAC9E,aAAa,EAAE,MAAM,CAAC;IACtB,MAAM,EAAE,MAAM,CAAC;IACf,+EAA+E;IAC/E,OAAO,EAAE,OAAO,CAAC;IACjB;;;;;mFAK+E;IAC/E,IAAI,CAAC,EAAE;QACL;;;8EAGsE;QACtE,QAAQ,EAAE,KAAK,MAAM,EAAE,CAAC;KACzB,CAAC;CACH;AAED;;wEAEwE;AACxE,wBAAgB,YAAY,CAC1B,GAAG,EAAE,iBAAiB,GACrB,GAAG,IAAI,iBAAiB,GAAG;IAAE,IAAI,EAAE,WAAW,CAAC,iBAAiB,CAAC,MAAM,CAAC,CAAC,CAAA;CAAE,CAE7E;AAKD,eAAO,MAAM,QAAQ,EAAE,MAAM,CAAC,WAAW,EAAE,iBAAiB,CAuC3D,CAAC;AAEF,4EAA4E;AAC5E,eAAO,MAAM,WAAW,EAAE,iBAAuC,CAAC;AAClE,iDAAiD;AACjD,MAAM,MAAM,UAAU,GAAG,iBAAiB,CAAC;AAE3C,iCAAiC;AACjC,wBAAgB,UAAU,CAAC,IAAI,EAAE,WAAW,GAAG,iBAAiB,CAE/D;AAED,8EAA8E;AAC9E,wBAAgB,aAAa,IAAI,iBAAiB,CAEjD;AAED,8EAA8E;AAC9E,wBAAgB,YAAY,CAAC,GAAG,GAAE,iBAAmC,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAE9F;AAED,yEAAyE;AACzE,wBAAgB,YAAY,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,GAAG,MAAM,CAG5D"}
|
package/dist/networks.js
ADDED
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Settlement network registry. One switch (`SETTLEMENT_NETWORK`) selects the chain
|
|
3
|
+
* the whole rail tolls on — the x402 quote, the settlement body, the discovery
|
|
4
|
+
* manifest, and the buyer client all read the active network from here, so moving
|
|
5
|
+
* between Arc testnet, Base Sepolia, and Base mainnet is an env change, not a code
|
|
6
|
+
* change.
|
|
7
|
+
*
|
|
8
|
+
* Source of truth for every constant: the @circle-fin/x402-batching SDK
|
|
9
|
+
* (dist/client chain configs) and circlefin/arc-nanopayments. Kept here, not
|
|
10
|
+
* imported from the SDK, so `shared` carries no dependency on it — the SDK is
|
|
11
|
+
* lazy-loaded only in gateway mode (see tollgate/x402.ts, wayfarer/gateway.ts).
|
|
12
|
+
*
|
|
13
|
+
* NOTE: the rail is Circle Gateway on every entry. "Swappable" here means swappable
|
|
14
|
+
* across Circle's chains, NOT across facilitators — there is deliberately one rail.
|
|
15
|
+
*/
|
|
16
|
+
import { getConfig } from "./config.js";
|
|
17
|
+
/** Field-presence capability gate — true iff this network ships the Memo predeploy.
|
|
18
|
+
* The settle path narrows on THIS, never on `chainName`, so adding a memo-capable
|
|
19
|
+
* chain (or swapping to a memo-less one) needs no settle-path edit. */
|
|
20
|
+
export function supportsMemo(net) {
|
|
21
|
+
return net.memo !== undefined;
|
|
22
|
+
}
|
|
23
|
+
const TESTNET_FACILITATOR = "https://gateway-api-testnet.circle.com";
|
|
24
|
+
const MAINNET_FACILITATOR = "https://gateway-api.circle.com";
|
|
25
|
+
export const NETWORKS = {
|
|
26
|
+
arcTestnet: {
|
|
27
|
+
chainName: "arcTestnet",
|
|
28
|
+
network: "eip155:5042002",
|
|
29
|
+
chainId: 5042002,
|
|
30
|
+
usdc: "0x3600000000000000000000000000000000000000",
|
|
31
|
+
// Verified on-chain 2026-06-19 (live Arc memo settle): the Arc-testnet USDC
|
|
32
|
+
// names itself "USDC"/"2", NOT the mainnet FiatToken "USD Coin". This is the
|
|
33
|
+
// EIP-712 domain the buyer's raw EIP-3009 authorization is signed against.
|
|
34
|
+
usdcName: "USDC",
|
|
35
|
+
usdcVersion: "2",
|
|
36
|
+
gatewayWallet: "0x0077777d7EBA4688BDeF3E311b846F25870A19B9",
|
|
37
|
+
gatewayApiUrl: TESTNET_FACILITATOR,
|
|
38
|
+
rpcUrl: "https://rpc.testnet.arc.network",
|
|
39
|
+
testnet: true,
|
|
40
|
+
// Arc ships the Memo + CallFrom predeploys; Base does not. Set here, gated on
|
|
41
|
+
// presence at settle time → a swap to Base drops memos with no settle-path edit.
|
|
42
|
+
memo: { contract: "0x5294E9927c3306DcBaDb03fe70b92e01cCede505" },
|
|
43
|
+
},
|
|
44
|
+
baseSepolia: {
|
|
45
|
+
chainName: "baseSepolia",
|
|
46
|
+
network: "eip155:84532",
|
|
47
|
+
chainId: 84532,
|
|
48
|
+
usdc: "0x036CbD53842c5426634e7929541eC2318f3dCF7e",
|
|
49
|
+
gatewayWallet: "0x0077777d7EBA4688BDeF3E311b846F25870A19B9",
|
|
50
|
+
gatewayApiUrl: TESTNET_FACILITATOR,
|
|
51
|
+
rpcUrl: "https://sepolia-preconf.base.org",
|
|
52
|
+
testnet: true,
|
|
53
|
+
},
|
|
54
|
+
base: {
|
|
55
|
+
chainName: "base",
|
|
56
|
+
network: "eip155:8453",
|
|
57
|
+
chainId: 8453,
|
|
58
|
+
usdc: "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913",
|
|
59
|
+
gatewayWallet: "0x77777777Dcc4d5A8B6E418Fd04D8997ef11000eE",
|
|
60
|
+
gatewayApiUrl: MAINNET_FACILITATOR,
|
|
61
|
+
rpcUrl: "https://mainnet.base.org",
|
|
62
|
+
testnet: false,
|
|
63
|
+
},
|
|
64
|
+
};
|
|
65
|
+
/** Back-compat alias for the original single-network export + interface. */
|
|
66
|
+
export const ARC_TESTNET = NETWORKS.arcTestnet;
|
|
67
|
+
/** Look up a network by name. */
|
|
68
|
+
export function getNetwork(name) {
|
|
69
|
+
return NETWORKS[name];
|
|
70
|
+
}
|
|
71
|
+
/** The network the gate is configured to settle on (`SETTLEMENT_NETWORK`). */
|
|
72
|
+
export function activeNetwork() {
|
|
73
|
+
return NETWORKS[getConfig().SETTLEMENT_NETWORK];
|
|
74
|
+
}
|
|
75
|
+
/** The Gateway batching x402 `extra` block, naming the verifying contract. */
|
|
76
|
+
export function gatewayExtra(net = activeNetwork()) {
|
|
77
|
+
return { name: "GatewayWalletBatched", version: "1", verifyingContract: net.gatewayWallet };
|
|
78
|
+
}
|
|
79
|
+
/** Convert a decimal USDC string/number to atomic units (6 decimals). */
|
|
80
|
+
export function toAtomicUsdc(amount) {
|
|
81
|
+
const n = typeof amount === "string" ? parseFloat(amount.replace("$", "")) : amount;
|
|
82
|
+
return Math.round(n * 1_000_000).toString();
|
|
83
|
+
}
|
|
84
|
+
//# sourceMappingURL=networks.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"networks.js","sourceRoot":"","sources":["../src/networks.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AACH,OAAO,EAAE,SAAS,EAAE,MAAM,aAAa,CAAC;AA+CxC;;wEAEwE;AACxE,MAAM,UAAU,YAAY,CAC1B,GAAsB;IAEtB,OAAO,GAAG,CAAC,IAAI,KAAK,SAAS,CAAC;AAChC,CAAC;AAED,MAAM,mBAAmB,GAAG,wCAAwC,CAAC;AACrE,MAAM,mBAAmB,GAAG,gCAAgC,CAAC;AAE7D,MAAM,CAAC,MAAM,QAAQ,GAA2C;IAC9D,UAAU,EAAE;QACV,SAAS,EAAE,YAAY;QACvB,OAAO,EAAE,gBAAgB;QACzB,OAAO,EAAE,OAAO;QAChB,IAAI,EAAE,4CAA4C;QAClD,4EAA4E;QAC5E,6EAA6E;QAC7E,2EAA2E;QAC3E,QAAQ,EAAE,MAAM;QAChB,WAAW,EAAE,GAAG;QAChB,aAAa,EAAE,4CAA4C;QAC3D,aAAa,EAAE,mBAAmB;QAClC,MAAM,EAAE,iCAAiC;QACzC,OAAO,EAAE,IAAI;QACb,8EAA8E;QAC9E,iFAAiF;QACjF,IAAI,EAAE,EAAE,QAAQ,EAAE,4CAA4C,EAAE;KACjE;IACD,WAAW,EAAE;QACX,SAAS,EAAE,aAAa;QACxB,OAAO,EAAE,cAAc;QACvB,OAAO,EAAE,KAAK;QACd,IAAI,EAAE,4CAA4C;QAClD,aAAa,EAAE,4CAA4C;QAC3D,aAAa,EAAE,mBAAmB;QAClC,MAAM,EAAE,kCAAkC;QAC1C,OAAO,EAAE,IAAI;KACd;IACD,IAAI,EAAE;QACJ,SAAS,EAAE,MAAM;QACjB,OAAO,EAAE,aAAa;QACtB,OAAO,EAAE,IAAI;QACb,IAAI,EAAE,4CAA4C;QAClD,aAAa,EAAE,4CAA4C;QAC3D,aAAa,EAAE,mBAAmB;QAClC,MAAM,EAAE,0BAA0B;QAClC,OAAO,EAAE,KAAK;KACf;CACF,CAAC;AAEF,4EAA4E;AAC5E,MAAM,CAAC,MAAM,WAAW,GAAsB,QAAQ,CAAC,UAAU,CAAC;AAIlE,iCAAiC;AACjC,MAAM,UAAU,UAAU,CAAC,IAAiB;IAC1C,OAAO,QAAQ,CAAC,IAAI,CAAC,CAAC;AACxB,CAAC;AAED,8EAA8E;AAC9E,MAAM,UAAU,aAAa;IAC3B,OAAO,QAAQ,CAAC,SAAS,EAAE,CAAC,kBAAkB,CAAC,CAAC;AAClD,CAAC;AAED,8EAA8E;AAC9E,MAAM,UAAU,YAAY,CAAC,MAAyB,aAAa,EAAE;IACnE,OAAO,EAAE,IAAI,EAAE,sBAAsB,EAAE,OAAO,EAAE,GAAG,EAAE,iBAAiB,EAAE,GAAG,CAAC,aAAa,EAAE,CAAC;AAC9F,CAAC;AAED,yEAAyE;AACzE,MAAM,UAAU,YAAY,CAAC,MAAuB;IAClD,MAAM,CAAC,GAAG,OAAO,MAAM,KAAK,QAAQ,CAAC,CAAC,CAAC,UAAU,CAAC,MAAM,CAAC,OAAO,CAAC,GAAG,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC;IACpF,OAAO,IAAI,CAAC,KAAK,CAAC,CAAC,GAAG,SAAS,CAAC,CAAC,QAAQ,EAAE,CAAC;AAC9C,CAAC"}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import type { ObservationEvent, ObservationSink } from "./types.ts";
|
|
2
|
+
/** Records nothing. The default — observability is opt-in. */
|
|
3
|
+
export declare function noopObservationSink(): ObservationSink;
|
|
4
|
+
/** Append-only JSONL file (dev / single-box). Mirrors `jsonlSink`. */
|
|
5
|
+
export declare function jsonlObservationSink(path?: string): ObservationSink;
|
|
6
|
+
/**
|
|
7
|
+
* Supabase-backed sink. One row per observation: `id` (primary key, idempotent
|
|
8
|
+
* retry), `at` (indexed, for ordering + TTL sweeps), `publisher` (top-level so the
|
|
9
|
+
* audit BFF filters server-side), and the whole `ObservationEvent` as jsonb `data`.
|
|
10
|
+
* The downstream owns the table + its retention policy; this only writes.
|
|
11
|
+
*/
|
|
12
|
+
export declare function supabaseObservationSink(): ObservationSink;
|
|
13
|
+
/**
|
|
14
|
+
* Pick the ObservationSink the config asks for. `off` (default) → no-op; `jsonl`
|
|
15
|
+
* → local file; `supabase` → the shared table. One env var, like `getSink`.
|
|
16
|
+
*/
|
|
17
|
+
export declare function getObservationSink(): ObservationSink;
|
|
18
|
+
/** In-memory sink, handy for tests — exposes what was recorded. */
|
|
19
|
+
export declare function memoryObservationSink(seed?: ObservationEvent[]): ObservationSink & {
|
|
20
|
+
readonly recorded: ObservationEvent[];
|
|
21
|
+
};
|
|
22
|
+
//# sourceMappingURL=observationsink.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"observationsink.d.ts","sourceRoot":"","sources":["../src/observationsink.ts"],"names":[],"mappings":"AAeA,OAAO,KAAK,EAAE,gBAAgB,EAAE,eAAe,EAAE,MAAM,YAAY,CAAC;AAEpE,8DAA8D;AAC9D,wBAAgB,mBAAmB,IAAI,eAAe,CAMrD;AAED,sEAAsE;AACtE,wBAAgB,oBAAoB,CAAC,IAAI,CAAC,EAAE,MAAM,GAAG,eAAe,CAQnE;AAED;;;;;GAKG;AACH,wBAAgB,uBAAuB,IAAI,eAAe,CAkBzD;AAED;;;GAGG;AACH,wBAAgB,kBAAkB,IAAI,eAAe,CASpD;AAED,mEAAmE;AACnE,wBAAgB,qBAAqB,CAAC,IAAI,GAAE,gBAAgB,EAAO,GAAG,eAAe,GAAG;IACtF,QAAQ,CAAC,QAAQ,EAAE,gBAAgB,EAAE,CAAC;CACvC,CAQA"}
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ObservationSink implementations — the audit/observability plane's write side.
|
|
3
|
+
* A sibling of `eventsink.ts`, but for the *negative space* the settlement ledger
|
|
4
|
+
* never sees: crawlers served free, agents denied at the 402, agents that paid.
|
|
5
|
+
*
|
|
6
|
+
* The default is `off` (a no-op): the open core emits nothing unless a deploy
|
|
7
|
+
* opts in, so the gate's "humans read free at zero added cost" promise holds and
|
|
8
|
+
* no observation data accumulates without intent. A multi-tenant embedder
|
|
9
|
+
* (the control plane) sets OBSERVATIONS_BACKEND=supabase to stream observations into
|
|
10
|
+
* its own audit table — exactly how settlement events already flow gate → cloud.
|
|
11
|
+
*/
|
|
12
|
+
import { appendFile, mkdir } from "node:fs/promises";
|
|
13
|
+
import { dirname, resolve } from "node:path";
|
|
14
|
+
import { getConfig } from "./config.js";
|
|
15
|
+
import { supabaseRest } from "./supabase.js";
|
|
16
|
+
/** Records nothing. The default — observability is opt-in. */
|
|
17
|
+
export function noopObservationSink() {
|
|
18
|
+
return {
|
|
19
|
+
async record() {
|
|
20
|
+
/* intentionally empty */
|
|
21
|
+
},
|
|
22
|
+
};
|
|
23
|
+
}
|
|
24
|
+
/** Append-only JSONL file (dev / single-box). Mirrors `jsonlSink`. */
|
|
25
|
+
export function jsonlObservationSink(path) {
|
|
26
|
+
const file = resolve(path ?? getConfig().OBSERVATIONS_PATH);
|
|
27
|
+
return {
|
|
28
|
+
async record(observation) {
|
|
29
|
+
await mkdir(dirname(file), { recursive: true });
|
|
30
|
+
await appendFile(file, JSON.stringify(observation) + "\n", "utf8");
|
|
31
|
+
},
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* Supabase-backed sink. One row per observation: `id` (primary key, idempotent
|
|
36
|
+
* retry), `at` (indexed, for ordering + TTL sweeps), `publisher` (top-level so the
|
|
37
|
+
* audit BFF filters server-side), and the whole `ObservationEvent` as jsonb `data`.
|
|
38
|
+
* The downstream owns the table + its retention policy; this only writes.
|
|
39
|
+
*/
|
|
40
|
+
export function supabaseObservationSink() {
|
|
41
|
+
const table = getConfig().SUPABASE_OBSERVATIONS_TABLE;
|
|
42
|
+
return {
|
|
43
|
+
async record(observation) {
|
|
44
|
+
await supabaseRest(`/rest/v1/${table}?on_conflict=id`, {
|
|
45
|
+
method: "POST",
|
|
46
|
+
headers: { Prefer: "resolution=ignore-duplicates" },
|
|
47
|
+
body: JSON.stringify([
|
|
48
|
+
{
|
|
49
|
+
id: observation.id,
|
|
50
|
+
at: observation.at,
|
|
51
|
+
publisher: observation.publisherId ?? null,
|
|
52
|
+
data: observation,
|
|
53
|
+
},
|
|
54
|
+
]),
|
|
55
|
+
});
|
|
56
|
+
},
|
|
57
|
+
};
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* Pick the ObservationSink the config asks for. `off` (default) → no-op; `jsonl`
|
|
61
|
+
* → local file; `supabase` → the shared table. One env var, like `getSink`.
|
|
62
|
+
*/
|
|
63
|
+
export function getObservationSink() {
|
|
64
|
+
switch (getConfig().OBSERVATIONS_BACKEND) {
|
|
65
|
+
case "supabase":
|
|
66
|
+
return supabaseObservationSink();
|
|
67
|
+
case "jsonl":
|
|
68
|
+
return jsonlObservationSink();
|
|
69
|
+
default:
|
|
70
|
+
return noopObservationSink();
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
/** In-memory sink, handy for tests — exposes what was recorded. */
|
|
74
|
+
export function memoryObservationSink(seed = []) {
|
|
75
|
+
const recorded = [...seed];
|
|
76
|
+
return {
|
|
77
|
+
recorded,
|
|
78
|
+
async record(observation) {
|
|
79
|
+
recorded.push(observation);
|
|
80
|
+
},
|
|
81
|
+
};
|
|
82
|
+
}
|
|
83
|
+
//# sourceMappingURL=observationsink.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"observationsink.js","sourceRoot":"","sources":["../src/observationsink.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AACH,OAAO,EAAE,UAAU,EAAE,KAAK,EAAE,MAAM,kBAAkB,CAAC;AACrD,OAAO,EAAE,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAC7C,OAAO,EAAE,SAAS,EAAE,MAAM,aAAa,CAAC;AACxC,OAAO,EAAE,YAAY,EAAE,MAAM,eAAe,CAAC;AAG7C,8DAA8D;AAC9D,MAAM,UAAU,mBAAmB;IACjC,OAAO;QACL,KAAK,CAAC,MAAM;YACV,yBAAyB;QAC3B,CAAC;KACF,CAAC;AACJ,CAAC;AAED,sEAAsE;AACtE,MAAM,UAAU,oBAAoB,CAAC,IAAa;IAChD,MAAM,IAAI,GAAG,OAAO,CAAC,IAAI,IAAI,SAAS,EAAE,CAAC,iBAAiB,CAAC,CAAC;IAC5D,OAAO;QACL,KAAK,CAAC,MAAM,CAAC,WAAW;YACtB,MAAM,KAAK,CAAC,OAAO,CAAC,IAAI,CAAC,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;YAChD,MAAM,UAAU,CAAC,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC,WAAW,CAAC,GAAG,IAAI,EAAE,MAAM,CAAC,CAAC;QACrE,CAAC;KACF,CAAC;AACJ,CAAC;AAED;;;;;GAKG;AACH,MAAM,UAAU,uBAAuB;IACrC,MAAM,KAAK,GAAG,SAAS,EAAE,CAAC,2BAA2B,CAAC;IACtD,OAAO;QACL,KAAK,CAAC,MAAM,CAAC,WAAW;YACtB,MAAM,YAAY,CAAC,YAAY,KAAK,iBAAiB,EAAE;gBACrD,MAAM,EAAE,MAAM;gBACd,OAAO,EAAE,EAAE,MAAM,EAAE,8BAA8B,EAAE;gBACnD,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC;oBACnB;wBACE,EAAE,EAAE,WAAW,CAAC,EAAE;wBAClB,EAAE,EAAE,WAAW,CAAC,EAAE;wBAClB,SAAS,EAAE,WAAW,CAAC,WAAW,IAAI,IAAI;wBAC1C,IAAI,EAAE,WAAW;qBAClB;iBACF,CAAC;aACH,CAAC,CAAC;QACL,CAAC;KACF,CAAC;AACJ,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,kBAAkB;IAChC,QAAQ,SAAS,EAAE,CAAC,oBAAoB,EAAE,CAAC;QACzC,KAAK,UAAU;YACb,OAAO,uBAAuB,EAAE,CAAC;QACnC,KAAK,OAAO;YACV,OAAO,oBAAoB,EAAE,CAAC;QAChC;YACE,OAAO,mBAAmB,EAAE,CAAC;IACjC,CAAC;AACH,CAAC;AAED,mEAAmE;AACnE,MAAM,UAAU,qBAAqB,CAAC,OAA2B,EAAE;IAGjE,MAAM,QAAQ,GAAG,CAAC,GAAG,IAAI,CAAC,CAAC;IAC3B,OAAO;QACL,QAAQ;QACR,KAAK,CAAC,MAAM,CAAC,WAAW;YACtB,QAAQ,CAAC,IAAI,CAAC,WAAW,CAAC,CAAC;QAC7B,CAAC;KACF,CAAC;AACJ,CAAC"}
|
|
@@ -0,0 +1,209 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The publisher-resolution seam.
|
|
3
|
+
*
|
|
4
|
+
* One `PublisherConfig` is everything the gate needs to serve a request for the
|
|
5
|
+
* protected publisher: where to proxy, what to charge, who to pay, how to identify
|
|
6
|
+
* itself when it signs receipts, and the secret it uses to report earnings back.
|
|
7
|
+
*
|
|
8
|
+
* A `PublisherResolver` maps the gate's inbound `Host` to that config. The gate core
|
|
9
|
+
* is single-tenant: the reference resolver (`envPublisherResolver`, in the tollgate
|
|
10
|
+
* package) builds one publisher from env and answers EVERY host with it. The
|
|
11
|
+
* resolver is an interface only so a downstream service can front a different
|
|
12
|
+
* publisher by injecting its own — without forking this core. Dependency points
|
|
13
|
+
* one way: a resolver impl may depend on this interface; this interface depends on
|
|
14
|
+
* nothing about how the config is sourced.
|
|
15
|
+
*/
|
|
16
|
+
import type { CreditsResolver, TollKind, Usdc, WalletAddress } from "./types.ts";
|
|
17
|
+
/**
|
|
18
|
+
* A settlement leg beyond the primary author payment — a secondary, direct
|
|
19
|
+
* buyer→recipient transfer attached to a toll. Each leg settles as its OWN x402
|
|
20
|
+
* authorization (its own `payTo`, `amount`, nonce): never pooled, never routed
|
|
21
|
+
* through a gate-held wallet, and **additive** — attaching a leg never reduces the
|
|
22
|
+
* author's amount. The protocol assigns `role` no meaning; it is an opaque label
|
|
23
|
+
* the ledger and dashboards group by (e.g. a co-author split, or a downstream
|
|
24
|
+
* resolver's own secondary payee). The single-tenant core attaches none.
|
|
25
|
+
*/
|
|
26
|
+
export interface PayoutLeg {
|
|
27
|
+
/** Opaque grouping label for the ledger/dashboard. No protocol meaning. */
|
|
28
|
+
role: string;
|
|
29
|
+
/** Direct recipient of this leg — a distinct buyer→payTo transfer. */
|
|
30
|
+
payTo: WalletAddress;
|
|
31
|
+
/** Atomic micro-USDC, integer string. Added on top of the author price. */
|
|
32
|
+
amount: string;
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* Per-publisher crawler policy — the tri-state surface a control plane stores.
|
|
36
|
+
* UA fragments, matched case-insensitively as substrings (same mechanics as
|
|
37
|
+
* `seoAllowlist`). Everything unlisted is CHARGED (the stock toll — absence of
|
|
38
|
+
* this field is byte-identical to today). Fragments are spoofable; verified
|
|
39
|
+
* crawler identity (Web Bot Auth) is a later hardening that swaps the matcher,
|
|
40
|
+
* not this shape.
|
|
41
|
+
*/
|
|
42
|
+
export interface CrawlerPolicy {
|
|
43
|
+
/** Fragments that read FREE. Merged with the deprecated `seoAllowlist`. */
|
|
44
|
+
allow: string[];
|
|
45
|
+
/**
|
|
46
|
+
* Fragments REFUSED outright on gateable routes — 403 even if the caller
|
|
47
|
+
* presents payment. Checked before classification, so payment intent can
|
|
48
|
+
* never buy past a block. Wins over `allow` on overlap (fail-safe).
|
|
49
|
+
*/
|
|
50
|
+
block: string[];
|
|
51
|
+
/**
|
|
52
|
+
* Fragments the publisher explicitly CHARGES. The classifier already tolls
|
|
53
|
+
* agents it recognizes (known-agent UA or declared intent); this list extends
|
|
54
|
+
* that recognition to crawlers the conservative default would let read free
|
|
55
|
+
* (browser-shaped or ambiguous UAs). Precedence: block > allow > charge.
|
|
56
|
+
* Absent → recognition is the classifier's default set, unchanged.
|
|
57
|
+
*/
|
|
58
|
+
charge?: string[];
|
|
59
|
+
}
|
|
60
|
+
export interface PublisherConfig {
|
|
61
|
+
/**
|
|
62
|
+
* Stable identifier for this publisher; tags attributed events and logs. The
|
|
63
|
+
* single-tenant default uses `"default"`.
|
|
64
|
+
*/
|
|
65
|
+
id: string;
|
|
66
|
+
/** The publisher origin the gate reverse-proxies article requests to. */
|
|
67
|
+
originUrl: string;
|
|
68
|
+
/**
|
|
69
|
+
* Path prefixes (no leading slash, e.g. `["essays", "posts"]`) whose articles
|
|
70
|
+
* are gateable. A request outside every prefix passes straight through, free.
|
|
71
|
+
*/
|
|
72
|
+
articlePrefixes: string[];
|
|
73
|
+
/**
|
|
74
|
+
* Base price for a single read, in whole USDC. A citation is priced up from
|
|
75
|
+
* this (see the tollgate's `quote`). Branded `Usdc` so it can't be mixed with a
|
|
76
|
+
* raw number in money math.
|
|
77
|
+
*/
|
|
78
|
+
price: Usdc;
|
|
79
|
+
/**
|
|
80
|
+
* Citation price as a multiple of `price` (a citation has downstream reach, so
|
|
81
|
+
* it's typically priced up). Both kinds resolve to the same payees; only the
|
|
82
|
+
* amount differs. 1 prices a citation the same as a read. The single-tenant
|
|
83
|
+
* default sources this from `CITATION_MULTIPLIER`.
|
|
84
|
+
*/
|
|
85
|
+
citationMultiplier: number;
|
|
86
|
+
/**
|
|
87
|
+
* Slug → credits graph: who gets paid for this article, and in what shares.
|
|
88
|
+
* THE publisher-agnostic seam (see `CreditsResolver`). An HTTP API, a static
|
|
89
|
+
* fixture, or a database — the gate doesn't care.
|
|
90
|
+
*/
|
|
91
|
+
credits: CreditsResolver;
|
|
92
|
+
/**
|
|
93
|
+
* issuer === audience for Citation License Tokens minted on this publisher's
|
|
94
|
+
* behalf (e.g. `naulon:<host>`). Scoping licenses to the publisher identity is
|
|
95
|
+
* what stops a receipt minted for one publisher from unlocking another.
|
|
96
|
+
*/
|
|
97
|
+
licenseIdentity: string;
|
|
98
|
+
/**
|
|
99
|
+
* HMAC secret for the settlement emit to this publisher's earnings ledger.
|
|
100
|
+
* Undefined leaves the emit dark — the gate still tolls and serves; it just
|
|
101
|
+
* doesn't report earnings. (Keeps the no-creds mock loop working, per the hard
|
|
102
|
+
* rule that the loop must run with no secrets.)
|
|
103
|
+
*/
|
|
104
|
+
settlementSecret?: string;
|
|
105
|
+
/**
|
|
106
|
+
* Static per-tenant credential the gate presents to the origin on every proxied
|
|
107
|
+
* fetch, as `X-Naulon-Origin-Auth: <value>`, so an origin behind its own bot/rate
|
|
108
|
+
* edge can recognize fleet traffic and skip challenging it. Undefined = origin
|
|
109
|
+
* requests unauthenticated (default; fine for origins with no edge, or that
|
|
110
|
+
* allowlist the fleet by IP instead). Sent ONLY when originUrl is https — never
|
|
111
|
+
* leak a bearer over cleartext.
|
|
112
|
+
*/
|
|
113
|
+
originAuthSecret?: string;
|
|
114
|
+
/**
|
|
115
|
+
* Verified search / discovery crawler UA fragments that read FREE for this
|
|
116
|
+
* publisher — the SEO allowlist. A request whose user-agent contains one of
|
|
117
|
+
* these is classified human (a discovery read), so a publisher's indexing is
|
|
118
|
+
* never tolled into a deindex. Honored by `classify` ahead of the known-agent
|
|
119
|
+
* list, matched case-insensitively. Undefined leaves only the classifier's
|
|
120
|
+
* global defaults in effect; the single-tenant resolver leaves it unset.
|
|
121
|
+
*/
|
|
122
|
+
seoAllowlist?: string[];
|
|
123
|
+
/**
|
|
124
|
+
* Tri-state crawler policy (allow / charge-by-default / block). Unset — the
|
|
125
|
+
* single-tenant default — leaves only `seoAllowlist` + classifier defaults in
|
|
126
|
+
* effect, byte-identical to before this field existed.
|
|
127
|
+
*/
|
|
128
|
+
crawlerPolicy?: CrawlerPolicy;
|
|
129
|
+
/**
|
|
130
|
+
* What the toll covers. Absent / {mode:"prefixes"} → the stock articlePrefixes
|
|
131
|
+
* matcher (byte-identical). {mode:"site"} → every path tolls, slug = the full
|
|
132
|
+
* decoded pathname; discovery surfaces (robots/sitemap/feeds/favicon), static
|
|
133
|
+
* assets, and gate control routes are hard-excluded regardless — tolling
|
|
134
|
+
* discovery would starve the catalog agents buy from. excludePrefixes adds
|
|
135
|
+
* publisher-chosen free sections on top (no leading slash, like articlePrefixes).
|
|
136
|
+
*/
|
|
137
|
+
gateScope?: {
|
|
138
|
+
mode: "prefixes";
|
|
139
|
+
} | {
|
|
140
|
+
mode: "site";
|
|
141
|
+
excludePrefixes: string[];
|
|
142
|
+
};
|
|
143
|
+
/**
|
|
144
|
+
* Optional hook: additional settlement legs for a priced toll, beyond the author
|
|
145
|
+
* payment. Given the resolved `price` (whole USDC) and `kind`, return any extra
|
|
146
|
+
* direct buyer→recipient legs to settle alongside the author. Each is additive
|
|
147
|
+
* (the author leg is unaffected) and settled as its own authorization. Leaving
|
|
148
|
+
* this unset — the single-tenant default — yields no extra legs, so the 402 and
|
|
149
|
+
* the settle path are byte-identical to a plain single-author toll. A downstream
|
|
150
|
+
* resolver (e.g. a multi-tenant control plane charging a secondary fee) populates
|
|
151
|
+
* it; the amount math (flat, percentage, tiered, capped — whatever) lives entirely
|
|
152
|
+
* in that resolver, never in this core.
|
|
153
|
+
*/
|
|
154
|
+
extraLegs?: (price: Usdc, kind: TollKind) => PayoutLeg[];
|
|
155
|
+
/**
|
|
156
|
+
* Pay co-authors directly on-chain (split-at-source) instead of routing the whole
|
|
157
|
+
* toll to the primary author. OFF by default (and irrelevant for a single-author
|
|
158
|
+
* article): the author leg settles the FULL price to the highest-share payee and
|
|
159
|
+
* the rest of the co-author split is recorded on the event for the publisher to
|
|
160
|
+
* reconcile off-protocol — the stock behavior, byte-identical on the wire.
|
|
161
|
+
*
|
|
162
|
+
* When ON and an article has >1 payee, the gate reduces the primary's synchronous
|
|
163
|
+
* (content-gating) leg to the primary's OWN share and emits one DEFERRED leg per
|
|
164
|
+
* other co-author — each a direct buyer→co-author transfer drained on-chain like
|
|
165
|
+
* any extra leg. The buyer's total is unchanged (the price is DIVIDED, not added
|
|
166
|
+
* to); only the recipients change. This keeps a genuinely multi-author payout
|
|
167
|
+
* custody-free: no author ever holds another author's cut. Opt-in because some
|
|
168
|
+
* publishers (a publication paying its writers off-protocol) want the single
|
|
169
|
+
* recipient; the credits graph decides who, this flag decides whether on-chain.
|
|
170
|
+
*/
|
|
171
|
+
coauthorSplit?: boolean;
|
|
172
|
+
/**
|
|
173
|
+
* Optional hook: the reconciliation id to stamp into the on-chain `Memo` for a
|
|
174
|
+
* priced toll (Arc only). Given the article `slug` and `kind`, return the id that
|
|
175
|
+
* ties this settlement to a citation / license for off-chain reconciliation —
|
|
176
|
+
* carried straight onto `Quote.memoId`, then keccak256'd to the indexed `bytes32`
|
|
177
|
+
* lookup key on settle (see the tollgate's `toMemoId`). Return `undefined` (or
|
|
178
|
+
* leave the hook unset) to stamp nothing: the settle path falls back to the
|
|
179
|
+
* authorization nonce and is byte-identical to the stock Gateway toll, so the
|
|
180
|
+
* single-tenant default is unaffected. The open-core gate never INVENTS an id —
|
|
181
|
+
* a downstream control plane owns the format,
|
|
182
|
+
* exactly like `extraLegs` owns the secondary-leg math.
|
|
183
|
+
*/
|
|
184
|
+
memoId?: (ctx: {
|
|
185
|
+
slug: string;
|
|
186
|
+
kind: TollKind;
|
|
187
|
+
}) => string | undefined;
|
|
188
|
+
/**
|
|
189
|
+
* Publisher is paused (e.g. a billing lapse upstream) — serve the origin straight
|
|
190
|
+
* through, FREE and untolled, instead of darking the site. Suspension must never
|
|
191
|
+
* turn a live publisher's readers away with an error; the gate simply stops
|
|
192
|
+
* earning until it's lifted. A resolver that has no paused state leaves this unset
|
|
193
|
+
* (false), and the gate tolls normally. Distinct from an UNKNOWN host (resolver
|
|
194
|
+
* returns undefined → the gate's not-served response): a known-but-paused
|
|
195
|
+
* publisher still has an origin to serve.
|
|
196
|
+
*/
|
|
197
|
+
suspended?: boolean;
|
|
198
|
+
}
|
|
199
|
+
export interface PublisherResolver {
|
|
200
|
+
/**
|
|
201
|
+
* Resolve the gate's inbound `Host` to the publisher it fronts. The
|
|
202
|
+
* single-tenant default ignores `host` and returns its one config for every
|
|
203
|
+
* request; a DB-backed impl looks the host up and may cache. Return `undefined`
|
|
204
|
+
* for a host this resolver does not recognize — the gate decides how to respond
|
|
205
|
+
* to an unknown host (it must not leak another publisher's config or misroute).
|
|
206
|
+
*/
|
|
207
|
+
resolve(host: string): Promise<PublisherConfig | undefined>;
|
|
208
|
+
}
|
|
209
|
+
//# sourceMappingURL=publisher.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"publisher.d.ts","sourceRoot":"","sources":["../src/publisher.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AACH,OAAO,KAAK,EAAE,eAAe,EAAE,QAAQ,EAAE,IAAI,EAAE,aAAa,EAAE,MAAM,YAAY,CAAC;AAEjF;;;;;;;;GAQG;AACH,MAAM,WAAW,SAAS;IACxB,2EAA2E;IAC3E,IAAI,EAAE,MAAM,CAAC;IACb,sEAAsE;IACtE,KAAK,EAAE,aAAa,CAAC;IACrB,2EAA2E;IAC3E,MAAM,EAAE,MAAM,CAAC;CAChB;AAED;;;;;;;GAOG;AACH,MAAM,WAAW,aAAa;IAC5B,2EAA2E;IAC3E,KAAK,EAAE,MAAM,EAAE,CAAC;IAChB;;;;OAIG;IACH,KAAK,EAAE,MAAM,EAAE,CAAC;IAChB;;;;;;OAMG;IACH,MAAM,CAAC,EAAE,MAAM,EAAE,CAAC;CACnB;AAED,MAAM,WAAW,eAAe;IAC9B;;;OAGG;IACH,EAAE,EAAE,MAAM,CAAC;IACX,yEAAyE;IACzE,SAAS,EAAE,MAAM,CAAC;IAClB;;;OAGG;IACH,eAAe,EAAE,MAAM,EAAE,CAAC;IAC1B;;;;OAIG;IACH,KAAK,EAAE,IAAI,CAAC;IACZ;;;;;OAKG;IACH,kBAAkB,EAAE,MAAM,CAAC;IAC3B;;;;OAIG;IACH,OAAO,EAAE,eAAe,CAAC;IACzB;;;;OAIG;IACH,eAAe,EAAE,MAAM,CAAC;IACxB;;;;;OAKG;IACH,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B;;;;;;;OAOG;IACH,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B;;;;;;;OAOG;IACH,YAAY,CAAC,EAAE,MAAM,EAAE,CAAC;IACxB;;;;OAIG;IACH,aAAa,CAAC,EAAE,aAAa,CAAC;IAC9B;;;;;;;OAOG;IACH,SAAS,CAAC,EAAE;QAAE,IAAI,EAAE,UAAU,CAAA;KAAE,GAAG;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,eAAe,EAAE,MAAM,EAAE,CAAA;KAAE,CAAC;IAC/E;;;;;;;;;;OAUG;IACH,SAAS,CAAC,EAAE,CAAC,KAAK,EAAE,IAAI,EAAE,IAAI,EAAE,QAAQ,KAAK,SAAS,EAAE,CAAC;IACzD;;;;;;;;;;;;;;;OAeG;IACH,aAAa,CAAC,EAAE,OAAO,CAAC;IACxB;;;;;;;;;;;OAWG;IACH,MAAM,CAAC,EAAE,CAAC,GAAG,EAAE;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,QAAQ,CAAA;KAAE,KAAK,MAAM,GAAG,SAAS,CAAC;IACvE;;;;;;;;OAQG;IACH,SAAS,CAAC,EAAE,OAAO,CAAC;CACrB;AAED,MAAM,WAAW,iBAAiB;IAChC;;;;;;OAMG;IACH,OAAO,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,eAAe,GAAG,SAAS,CAAC,CAAC;CAC7D"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"publisher.js","sourceRoot":"","sources":["../src/publisher.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* naulon → publisher settlement emit (the producer side of wire #3).
|
|
3
|
+
*
|
|
4
|
+
* After an x402 paid read settles on-chain, naulon reports it to the publisher's
|
|
5
|
+
* earnings ledger: POST ${ORIGIN_URL}/api/credits/settlement, HMAC-signed. The
|
|
6
|
+
* publisher's ledger is canonical; its author dashboard reads from these events,
|
|
7
|
+
* never from scraping chain balance. The request shape + signature recipe are
|
|
8
|
+
* specified by `buildSettlementBody` / `signSettlement` below.
|
|
9
|
+
*
|
|
10
|
+
* This module is PURE: it maps an AttributedEvent into the publisher's request shape and
|
|
11
|
+
* signs it. No fetch, no ambient clock — the caller passes the timestamp and
|
|
12
|
+
* owns delivery/retry (tollgate/src/settlementSink.ts). That keeps the money
|
|
13
|
+
* math and the signature recipe unit-testable offline.
|
|
14
|
+
*/
|
|
15
|
+
import { type TieBreak } from "./attribution.ts";
|
|
16
|
+
import type { AttributedEvent } from "./types.ts";
|
|
17
|
+
import { signSettlement, type SettlementBody } from "@naulon/sdk";
|
|
18
|
+
export { signSettlement };
|
|
19
|
+
export type { SettlementBody, SettlementSplit, SignedSettlement } from "@naulon/sdk";
|
|
20
|
+
/**
|
|
21
|
+
* Map a settled AttributedEvent into IA's settlement shape.
|
|
22
|
+
*
|
|
23
|
+
* Invariants this guarantees (IA rejects a violation):
|
|
24
|
+
* - Σ splits[].amount === grossAmount (dust-free, via splitMicro)
|
|
25
|
+
* - exactly one split is `primary: true`, and it is the on-chain recipient
|
|
26
|
+
* (the largest-share payee — mirrors x402.ts `primaryPayee`)
|
|
27
|
+
* - eventId === event.id (the stable, persisted UUID / license jti) so an
|
|
28
|
+
* at-least-once retry carries the same id and IA dedupes it.
|
|
29
|
+
*/
|
|
30
|
+
export declare function buildSettlementBody(event: AttributedEvent, chainId: number, tieBreak?: TieBreak): SettlementBody;
|
|
31
|
+
//# sourceMappingURL=settlementEmit.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"settlementEmit.d.ts","sourceRoot":"","sources":["../src/settlementEmit.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AACH,OAAO,EAAgB,KAAK,QAAQ,EAAE,MAAM,kBAAkB,CAAC;AAE/D,OAAO,KAAK,EAAE,eAAe,EAAiB,MAAM,YAAY,CAAC;AACjE,OAAO,EAAE,cAAc,EAAE,KAAK,cAAc,EAAwB,MAAM,aAAa,CAAC;AAOxF,OAAO,EAAE,cAAc,EAAE,CAAC;AAC1B,YAAY,EAAE,cAAc,EAAE,eAAe,EAAE,gBAAgB,EAAE,MAAM,aAAa,CAAC;AAMrF;;;;;;;;;GASG;AACH,wBAAgB,mBAAmB,CACjC,KAAK,EAAE,eAAe,EACtB,OAAO,EAAE,MAAM,EACf,QAAQ,GAAE,QAAmB,GAC5B,cAAc,CAiChB"}
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* naulon → publisher settlement emit (the producer side of wire #3).
|
|
3
|
+
*
|
|
4
|
+
* After an x402 paid read settles on-chain, naulon reports it to the publisher's
|
|
5
|
+
* earnings ledger: POST ${ORIGIN_URL}/api/credits/settlement, HMAC-signed. The
|
|
6
|
+
* publisher's ledger is canonical; its author dashboard reads from these events,
|
|
7
|
+
* never from scraping chain balance. The request shape + signature recipe are
|
|
8
|
+
* specified by `buildSettlementBody` / `signSettlement` below.
|
|
9
|
+
*
|
|
10
|
+
* This module is PURE: it maps an AttributedEvent into the publisher's request shape and
|
|
11
|
+
* signs it. No fetch, no ambient clock — the caller passes the timestamp and
|
|
12
|
+
* owns delivery/retry (tollgate/src/settlementSink.ts). That keeps the money
|
|
13
|
+
* math and the signature recipe unit-testable offline.
|
|
14
|
+
*/
|
|
15
|
+
import { primaryPayee } from "./attribution.js";
|
|
16
|
+
import { splitMicro } from "./attribution.js";
|
|
17
|
+
import { signSettlement } from "@naulon/sdk";
|
|
18
|
+
// The settlement WIRE CONTRACT (the body shape + the HMAC signer) now lives in
|
|
19
|
+
// @naulon/sdk — the verify side (verifySettlement) mirrors it from the same
|
|
20
|
+
// source. Re-exported so existing `@naulon/shared` importers are unchanged.
|
|
21
|
+
// `buildSettlementBody` below stays here: it consumes the gate-internal
|
|
22
|
+
// AttributedEvent + attribution math, which are not contract surface.
|
|
23
|
+
export { signSettlement };
|
|
24
|
+
const ZERO_ADDRESS = "0x0000000000000000000000000000000000000000";
|
|
25
|
+
const MICRO = 1_000_000;
|
|
26
|
+
/**
|
|
27
|
+
* Map a settled AttributedEvent into IA's settlement shape.
|
|
28
|
+
*
|
|
29
|
+
* Invariants this guarantees (IA rejects a violation):
|
|
30
|
+
* - Σ splits[].amount === grossAmount (dust-free, via splitMicro)
|
|
31
|
+
* - exactly one split is `primary: true`, and it is the on-chain recipient
|
|
32
|
+
* (the largest-share payee — mirrors x402.ts `primaryPayee`)
|
|
33
|
+
* - eventId === event.id (the stable, persisted UUID / license jti) so an
|
|
34
|
+
* at-least-once retry carries the same id and IA dedupes it.
|
|
35
|
+
*/
|
|
36
|
+
export function buildSettlementBody(event, chainId, tieBreak = "wallet") {
|
|
37
|
+
const grossMicro = Math.round(event.amount * MICRO);
|
|
38
|
+
const allocations = splitMicro(grossMicro, event.payees);
|
|
39
|
+
// The on-chain leg pays the largest-share payee; that same wallet is `paidTo`
|
|
40
|
+
// and the one split flagged primary. Uses the shared `primaryPayee` chooser so
|
|
41
|
+
// this record names exactly the wallet x402's requirement builder paid — same
|
|
42
|
+
// tie-break, single source of truth.
|
|
43
|
+
const primaryWallet = event.payees.length ? primaryPayee(event.payees, tieBreak) : undefined;
|
|
44
|
+
const splits = allocations.map((a, i) => ({
|
|
45
|
+
authorId: a.authorId,
|
|
46
|
+
wallet: a.wallet,
|
|
47
|
+
amount: String(a.micro),
|
|
48
|
+
weight: Math.round((event.payees[i]?.share ?? 0) * 1000),
|
|
49
|
+
...(a.wallet === primaryWallet ? { primary: true } : {}),
|
|
50
|
+
}));
|
|
51
|
+
const payer = event.payerAddress && event.payerAddress !== ZERO_ADDRESS ? event.payerAddress : null;
|
|
52
|
+
return {
|
|
53
|
+
eventId: event.id,
|
|
54
|
+
slug: event.slug,
|
|
55
|
+
txHash: event.settlementRef,
|
|
56
|
+
chainId,
|
|
57
|
+
currency: "USDC",
|
|
58
|
+
grossAmount: String(grossMicro),
|
|
59
|
+
paidTo: (primaryWallet ?? ZERO_ADDRESS),
|
|
60
|
+
payer,
|
|
61
|
+
settledAt: new Date(event.at).toISOString(),
|
|
62
|
+
splits,
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
//# sourceMappingURL=settlementEmit.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"settlementEmit.js","sourceRoot":"","sources":["../src/settlementEmit.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AACH,OAAO,EAAE,YAAY,EAAiB,MAAM,kBAAkB,CAAC;AAC/D,OAAO,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAC;AAE9C,OAAO,EAAE,cAAc,EAA6C,MAAM,aAAa,CAAC;AAExF,+EAA+E;AAC/E,4EAA4E;AAC5E,4EAA4E;AAC5E,wEAAwE;AACxE,sEAAsE;AACtE,OAAO,EAAE,cAAc,EAAE,CAAC;AAG1B,MAAM,YAAY,GAAG,4CAA4C,CAAC;AAElE,MAAM,KAAK,GAAG,SAAS,CAAC;AAExB;;;;;;;;;GASG;AACH,MAAM,UAAU,mBAAmB,CACjC,KAAsB,EACtB,OAAe,EACf,WAAqB,QAAQ;IAE7B,MAAM,UAAU,GAAG,IAAI,CAAC,KAAK,CAAC,KAAK,CAAC,MAAM,GAAG,KAAK,CAAC,CAAC;IACpD,MAAM,WAAW,GAAG,UAAU,CAAC,UAAU,EAAE,KAAK,CAAC,MAAM,CAAC,CAAC;IAEzD,8EAA8E;IAC9E,+EAA+E;IAC/E,8EAA8E;IAC9E,qCAAqC;IACrC,MAAM,aAAa,GAAG,KAAK,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC,CAAC,YAAY,CAAC,KAAK,CAAC,MAAM,EAAE,QAAQ,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC;IAE7F,MAAM,MAAM,GAAsB,WAAW,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC;QAC3D,QAAQ,EAAE,CAAC,CAAC,QAAQ;QACpB,MAAM,EAAE,CAAC,CAAC,MAAM;QAChB,MAAM,EAAE,MAAM,CAAC,CAAC,CAAC,KAAK,CAAC;QACvB,MAAM,EAAE,IAAI,CAAC,KAAK,CAAC,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,KAAK,IAAI,CAAC,CAAC,GAAG,IAAI,CAAC;QACxD,GAAG,CAAC,CAAC,CAAC,MAAM,KAAK,aAAa,CAAC,CAAC,CAAE,EAAE,OAAO,EAAE,IAAI,EAAY,CAAC,CAAC,CAAC,EAAE,CAAC;KACpE,CAAC,CAAC,CAAC;IAEJ,MAAM,KAAK,GACT,KAAK,CAAC,YAAY,IAAI,KAAK,CAAC,YAAY,KAAK,YAAY,CAAC,CAAC,CAAC,KAAK,CAAC,YAAY,CAAC,CAAC,CAAC,IAAI,CAAC;IAExF,OAAO;QACL,OAAO,EAAE,KAAK,CAAC,EAAE;QACjB,IAAI,EAAE,KAAK,CAAC,IAAI;QAChB,MAAM,EAAE,KAAK,CAAC,aAAa;QAC3B,OAAO;QACP,QAAQ,EAAE,MAAM;QAChB,WAAW,EAAE,MAAM,CAAC,UAAU,CAAC;QAC/B,MAAM,EAAE,CAAC,aAAa,IAAI,YAAY,CAAkB;QACxD,KAAK;QACL,SAAS,EAAE,IAAI,IAAI,CAAC,KAAK,CAAC,EAAE,CAAC,CAAC,WAAW,EAAE;QAC3C,MAAM;KACP,CAAC;AACJ,CAAC"}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Call the Supabase REST (PostgREST) API and return the parsed JSON body.
|
|
3
|
+
* Throws on any non-2xx so callers fail loud rather than silently losing a
|
|
4
|
+
* write. `path` is everything after the project URL, e.g.
|
|
5
|
+
* `/rest/v1/naulon_events?select=data&order=at.asc`.
|
|
6
|
+
*/
|
|
7
|
+
export declare function supabaseRest(path: string, init?: RequestInit): Promise<unknown>;
|
|
8
|
+
//# sourceMappingURL=supabase.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"supabase.d.ts","sourceRoot":"","sources":["../src/supabase.ts"],"names":[],"mappings":"AAwBA;;;;;GAKG;AACH,wBAAsB,YAAY,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,GAAE,WAAgB,GAAG,OAAO,CAAC,OAAO,CAAC,CAkBzF"}
|