@rubicon-caliga/core 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/dist/contract.d.ts +133 -0
- package/dist/contract.d.ts.map +1 -0
- package/dist/contract.js +2 -0
- package/dist/contract.js.map +1 -0
- package/dist/index.d.ts +6 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +6 -0
- package/dist/index.js.map +1 -0
- package/dist/money.d.ts +6 -0
- package/dist/money.d.ts.map +1 -0
- package/dist/money.js +15 -0
- package/dist/money.js.map +1 -0
- package/dist/pricing.d.ts +40 -0
- package/dist/pricing.d.ts.map +1 -0
- package/dist/pricing.js +25 -0
- package/dist/pricing.js.map +1 -0
- package/dist/protocol.d.ts +218 -0
- package/dist/protocol.d.ts.map +1 -0
- package/dist/protocol.js +2 -0
- package/dist/protocol.js.map +1 -0
- package/dist/session.d.ts +55 -0
- package/dist/session.d.ts.map +1 -0
- package/dist/session.js +48 -0
- package/dist/session.js.map +1 -0
- package/package.json +35 -0
- package/src/contract.ts +148 -0
- package/src/index.ts +5 -0
- package/src/money.ts +19 -0
- package/src/pricing.ts +66 -0
- package/src/protocol.ts +250 -0
- package/src/session.ts +101 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Rubicon
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
import type { AtomicAmount } from "./money.js";
|
|
2
|
+
/**
|
|
3
|
+
* Shared API contract between Rubicon (this gateway) and the rubicon-marketing
|
|
4
|
+
* Next.js application. Both repositories agree on these shapes, the article
|
|
5
|
+
* states, pricing units (atomic USDC, 1 USDC = 1_000_000), word-counting rules,
|
|
6
|
+
* wallet format, creator ownership, and seller-agent configuration.
|
|
7
|
+
*
|
|
8
|
+
* These types describe the shared persistent data model. The marketing app owns
|
|
9
|
+
* creator authentication and creator-facing CRUD; Rubicon reads published data
|
|
10
|
+
* and writes runtime read/earnings activity.
|
|
11
|
+
*/
|
|
12
|
+
/**
|
|
13
|
+
* Lifecycle of an article. Only `live` articles are consumable by buyer agents
|
|
14
|
+
* or visible in the public repository.
|
|
15
|
+
*/
|
|
16
|
+
export type ArticleState = "draft" | "live" | "paused" | "archived" | "deleted";
|
|
17
|
+
export declare const PUBLIC_ARTICLE_STATE: ArticleState;
|
|
18
|
+
export type WalletNetwork = string;
|
|
19
|
+
export interface Creator {
|
|
20
|
+
id: string;
|
|
21
|
+
username: string;
|
|
22
|
+
displayName: string;
|
|
23
|
+
createdAt: string;
|
|
24
|
+
}
|
|
25
|
+
export interface CreatorProfile {
|
|
26
|
+
creatorId: string;
|
|
27
|
+
bio?: string;
|
|
28
|
+
avatarUrl?: string;
|
|
29
|
+
}
|
|
30
|
+
export interface CreatorWallet {
|
|
31
|
+
creatorId: string;
|
|
32
|
+
/** Verified receiving wallet. Settlement always pays this address. */
|
|
33
|
+
address: `0x${string}`;
|
|
34
|
+
network: WalletNetwork;
|
|
35
|
+
verified: boolean;
|
|
36
|
+
}
|
|
37
|
+
export interface ArticleSection {
|
|
38
|
+
id: string;
|
|
39
|
+
articleId: string;
|
|
40
|
+
/** Stable slug used by buyer agents for navigation. */
|
|
41
|
+
sectionId: string;
|
|
42
|
+
heading: string;
|
|
43
|
+
level: number;
|
|
44
|
+
/** Zero-based word index where this section's body begins. */
|
|
45
|
+
wordStart: number;
|
|
46
|
+
wordCount: number;
|
|
47
|
+
ordinal: number;
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* Optional per-article seller-agent configuration. Agreed between Rubicon and
|
|
51
|
+
* rubicon-marketing; absent values fall back to gateway defaults.
|
|
52
|
+
*/
|
|
53
|
+
export interface SellerAgentConfig {
|
|
54
|
+
persona?: string;
|
|
55
|
+
/** Identifier of a configured model/provider, e.g. "anthropic:claude-opus-4-8". */
|
|
56
|
+
model?: string;
|
|
57
|
+
/** Extra navigation guidance the creator wants the seller agent to follow. */
|
|
58
|
+
guidance?: string;
|
|
59
|
+
}
|
|
60
|
+
export interface Article {
|
|
61
|
+
id: string;
|
|
62
|
+
creatorId: string;
|
|
63
|
+
title: string;
|
|
64
|
+
author: string;
|
|
65
|
+
state: ArticleState;
|
|
66
|
+
pricePerWordAtomic: AtomicAmount;
|
|
67
|
+
/** Optional creator-set cap on the total price of the article. */
|
|
68
|
+
maxArticlePriceAtomic?: AtomicAmount;
|
|
69
|
+
totalWords: number;
|
|
70
|
+
revision: number;
|
|
71
|
+
sellerAgentConfig?: SellerAgentConfig;
|
|
72
|
+
createdAt: string;
|
|
73
|
+
updatedAt: string;
|
|
74
|
+
}
|
|
75
|
+
export interface EarningsSummary {
|
|
76
|
+
creatorId: string;
|
|
77
|
+
articleId?: string;
|
|
78
|
+
wordsDelivered: number;
|
|
79
|
+
creatorAmountAtomic: AtomicAmount;
|
|
80
|
+
rubiconFeeAtomic: AtomicAmount;
|
|
81
|
+
}
|
|
82
|
+
export interface PaymentActivity {
|
|
83
|
+
paymentId: string;
|
|
84
|
+
sessionId: string;
|
|
85
|
+
articleId: string;
|
|
86
|
+
sequence: number;
|
|
87
|
+
amountAtomic: AtomicAmount;
|
|
88
|
+
creatorAmountAtomic: AtomicAmount;
|
|
89
|
+
rubiconFeeAtomic: AtomicAmount;
|
|
90
|
+
network?: string;
|
|
91
|
+
payTo?: `0x${string}`;
|
|
92
|
+
transactionHash?: string;
|
|
93
|
+
transactionHashes?: string[];
|
|
94
|
+
transferId?: string;
|
|
95
|
+
createdAt: string;
|
|
96
|
+
}
|
|
97
|
+
export interface SellerAgentMessageRecord {
|
|
98
|
+
id: string;
|
|
99
|
+
conversationId: string;
|
|
100
|
+
sessionId?: string;
|
|
101
|
+
articleId: string;
|
|
102
|
+
role: "buyer" | "seller";
|
|
103
|
+
content: string;
|
|
104
|
+
createdAt: string;
|
|
105
|
+
}
|
|
106
|
+
export interface StreamSessionRecord {
|
|
107
|
+
id: string;
|
|
108
|
+
articleId: string;
|
|
109
|
+
creatorId: string;
|
|
110
|
+
conversationId?: string;
|
|
111
|
+
state: string;
|
|
112
|
+
wordsPaid: number;
|
|
113
|
+
wordsDelivered: number;
|
|
114
|
+
paidAtomic: AtomicAmount;
|
|
115
|
+
budgetAtomic: AtomicAmount;
|
|
116
|
+
createdAt: string;
|
|
117
|
+
expiresAt: string;
|
|
118
|
+
}
|
|
119
|
+
export interface WordDeliveryRecord {
|
|
120
|
+
sessionId: string;
|
|
121
|
+
articleId: string;
|
|
122
|
+
sequence: number;
|
|
123
|
+
word: string;
|
|
124
|
+
priceAtomic: AtomicAmount;
|
|
125
|
+
paymentId: string;
|
|
126
|
+
createdAt: string;
|
|
127
|
+
}
|
|
128
|
+
export interface ApiError {
|
|
129
|
+
error: string;
|
|
130
|
+
message?: string;
|
|
131
|
+
details?: Record<string, unknown>;
|
|
132
|
+
}
|
|
133
|
+
//# sourceMappingURL=contract.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"contract.d.ts","sourceRoot":"","sources":["../src/contract.ts"],"names":[],"mappings":"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"}
|
package/dist/contract.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"contract.js","sourceRoot":"","sources":["../src/contract.ts"],"names":[],"mappings":"AAmBA,MAAM,CAAC,MAAM,oBAAoB,GAAiB,MAAM,CAAC"}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,YAAY,CAAC;AAC3B,cAAc,eAAe,CAAC;AAC9B,cAAc,cAAc,CAAC;AAC7B,cAAc,eAAe,CAAC;AAC9B,cAAc,cAAc,CAAC"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,YAAY,CAAC;AAC3B,cAAc,eAAe,CAAC;AAC9B,cAAc,cAAc,CAAC;AAC7B,cAAc,eAAe,CAAC;AAC9B,cAAc,cAAc,CAAC"}
|
package/dist/money.d.ts
ADDED
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
export declare const USDC_ATOMIC_UNITS = 1000000n;
|
|
2
|
+
export type AtomicAmount = `${bigint}`;
|
|
3
|
+
export declare function parseUsdcToAtomic(value: string): bigint;
|
|
4
|
+
export declare function formatAtomicUsdc(amount: bigint): string;
|
|
5
|
+
export declare function addBasisPoints(amount: bigint, basisPoints: number): bigint;
|
|
6
|
+
//# sourceMappingURL=money.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"money.d.ts","sourceRoot":"","sources":["../src/money.ts"],"names":[],"mappings":"AAAA,eAAO,MAAM,iBAAiB,WAAa,CAAC;AAE5C,MAAM,MAAM,YAAY,GAAG,GAAG,MAAM,EAAE,CAAC;AAEvC,wBAAgB,iBAAiB,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAIvD;AAED,wBAAgB,gBAAgB,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,CAIvD;AAED,wBAAgB,cAAc,CAAC,MAAM,EAAE,MAAM,EAAE,WAAW,EAAE,MAAM,GAAG,MAAM,CAE1E"}
|
package/dist/money.js
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
export const USDC_ATOMIC_UNITS = 1000000n;
|
|
2
|
+
export function parseUsdcToAtomic(value) {
|
|
3
|
+
const [whole = "0", fraction = ""] = value.split(".");
|
|
4
|
+
const paddedFraction = `${fraction}000000`.slice(0, 6);
|
|
5
|
+
return BigInt(whole) * USDC_ATOMIC_UNITS + BigInt(paddedFraction);
|
|
6
|
+
}
|
|
7
|
+
export function formatAtomicUsdc(amount) {
|
|
8
|
+
const whole = amount / USDC_ATOMIC_UNITS;
|
|
9
|
+
const fraction = `${amount % USDC_ATOMIC_UNITS}`.padStart(6, "0").replace(/0+$/, "");
|
|
10
|
+
return fraction.length > 0 ? `${whole}.${fraction}` : `${whole}`;
|
|
11
|
+
}
|
|
12
|
+
export function addBasisPoints(amount, basisPoints) {
|
|
13
|
+
return amount + (amount * BigInt(basisPoints)) / 10000n;
|
|
14
|
+
}
|
|
15
|
+
//# sourceMappingURL=money.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"money.js","sourceRoot":"","sources":["../src/money.ts"],"names":[],"mappings":"AAAA,MAAM,CAAC,MAAM,iBAAiB,GAAG,QAAU,CAAC;AAI5C,MAAM,UAAU,iBAAiB,CAAC,KAAa;IAC7C,MAAM,CAAC,KAAK,GAAG,GAAG,EAAE,QAAQ,GAAG,EAAE,CAAC,GAAG,KAAK,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC;IACtD,MAAM,cAAc,GAAG,GAAG,QAAQ,QAAQ,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC;IACvD,OAAO,MAAM,CAAC,KAAK,CAAC,GAAG,iBAAiB,GAAG,MAAM,CAAC,cAAc,CAAC,CAAC;AACpE,CAAC;AAED,MAAM,UAAU,gBAAgB,CAAC,MAAc;IAC7C,MAAM,KAAK,GAAG,MAAM,GAAG,iBAAiB,CAAC;IACzC,MAAM,QAAQ,GAAG,GAAG,MAAM,GAAG,iBAAiB,EAAE,CAAC,QAAQ,CAAC,CAAC,EAAE,GAAG,CAAC,CAAC,OAAO,CAAC,KAAK,EAAE,EAAE,CAAC,CAAC;IACrF,OAAO,QAAQ,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,GAAG,KAAK,IAAI,QAAQ,EAAE,CAAC,CAAC,CAAC,GAAG,KAAK,EAAE,CAAC;AACnE,CAAC;AAED,MAAM,UAAU,cAAc,CAAC,MAAc,EAAE,WAAmB;IAChE,OAAO,MAAM,GAAG,CAAC,MAAM,GAAG,MAAM,CAAC,WAAW,CAAC,CAAC,GAAG,MAAO,CAAC;AAC3D,CAAC"}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
export type MeteringUnit = "word";
|
|
2
|
+
/**
|
|
3
|
+
* The atomic content unit in Rubicon is exactly one word. A quote describes the
|
|
4
|
+
* price the creator earns for a single word and the amount the buyer must pay to
|
|
5
|
+
* release one additional word.
|
|
6
|
+
*
|
|
7
|
+
* Rubicon never bills in chunks. Circle/x402 may batch settlement internally,
|
|
8
|
+
* but the application-level accounting is always one word = one paid unit.
|
|
9
|
+
*/
|
|
10
|
+
export interface WordPriceQuote {
|
|
11
|
+
currency: "USDC";
|
|
12
|
+
meteringUnit: MeteringUnit;
|
|
13
|
+
/** Price the creator earns for one delivered word. */
|
|
14
|
+
pricePerWordAtomic: `${bigint}`;
|
|
15
|
+
/** Rubicon gateway fee in basis points. Defaults to 0. */
|
|
16
|
+
gatewayFeeBps: number;
|
|
17
|
+
/** Exact amount the buyer authorizes/sends to release one additional word. */
|
|
18
|
+
wordPaymentAtomic: `${bigint}`;
|
|
19
|
+
}
|
|
20
|
+
export interface WordUsageReport {
|
|
21
|
+
unit: MeteringUnit;
|
|
22
|
+
/** Number of individually delivered, individually paid words. */
|
|
23
|
+
wordsDelivered: number;
|
|
24
|
+
/** Full word price accruing to the creator (no Rubicon fee deducted). */
|
|
25
|
+
creatorAmountAtomic: `${bigint}`;
|
|
26
|
+
/** Rubicon fee. Zero by default — creators keep the full word price. */
|
|
27
|
+
rubiconFeeAtomic: `${bigint}`;
|
|
28
|
+
/** Total atomic USDC the buyer paid for these words. */
|
|
29
|
+
totalPaidAtomic: `${bigint}`;
|
|
30
|
+
}
|
|
31
|
+
export declare function quotePerWord(input: {
|
|
32
|
+
pricePerWordAtomic: bigint;
|
|
33
|
+
gatewayFeeBps?: number;
|
|
34
|
+
}): WordPriceQuote;
|
|
35
|
+
export declare function usageForWords(input: {
|
|
36
|
+
wordsDelivered: number;
|
|
37
|
+
pricePerWordAtomic: bigint;
|
|
38
|
+
gatewayFeeBps?: number;
|
|
39
|
+
}): WordUsageReport;
|
|
40
|
+
//# sourceMappingURL=pricing.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"pricing.d.ts","sourceRoot":"","sources":["../src/pricing.ts"],"names":[],"mappings":"AAEA,MAAM,MAAM,YAAY,GAAG,MAAM,CAAC;AAElC;;;;;;;GAOG;AACH,MAAM,WAAW,cAAc;IAC7B,QAAQ,EAAE,MAAM,CAAC;IACjB,YAAY,EAAE,YAAY,CAAC;IAC3B,sDAAsD;IACtD,kBAAkB,EAAE,GAAG,MAAM,EAAE,CAAC;IAChC,0DAA0D;IAC1D,aAAa,EAAE,MAAM,CAAC;IACtB,8EAA8E;IAC9E,iBAAiB,EAAE,GAAG,MAAM,EAAE,CAAC;CAChC;AAED,MAAM,WAAW,eAAe;IAC9B,IAAI,EAAE,YAAY,CAAC;IACnB,iEAAiE;IACjE,cAAc,EAAE,MAAM,CAAC;IACvB,yEAAyE;IACzE,mBAAmB,EAAE,GAAG,MAAM,EAAE,CAAC;IACjC,wEAAwE;IACxE,gBAAgB,EAAE,GAAG,MAAM,EAAE,CAAC;IAC9B,wDAAwD;IACxD,eAAe,EAAE,GAAG,MAAM,EAAE,CAAC;CAC9B;AAED,wBAAgB,YAAY,CAAC,KAAK,EAAE;IAClC,kBAAkB,EAAE,MAAM,CAAC;IAC3B,aAAa,CAAC,EAAE,MAAM,CAAC;CACxB,GAAG,cAAc,CAUjB;AAED,wBAAgB,aAAa,CAAC,KAAK,EAAE;IACnC,cAAc,EAAE,MAAM,CAAC;IACvB,kBAAkB,EAAE,MAAM,CAAC;IAC3B,aAAa,CAAC,EAAE,MAAM,CAAC;CACxB,GAAG,eAAe,CAWlB"}
|
package/dist/pricing.js
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { addBasisPoints } from "./money.js";
|
|
2
|
+
export function quotePerWord(input) {
|
|
3
|
+
const gatewayFeeBps = input.gatewayFeeBps ?? 0;
|
|
4
|
+
const wordPayment = addBasisPoints(input.pricePerWordAtomic, gatewayFeeBps);
|
|
5
|
+
return {
|
|
6
|
+
currency: "USDC",
|
|
7
|
+
meteringUnit: "word",
|
|
8
|
+
pricePerWordAtomic: `${input.pricePerWordAtomic}`,
|
|
9
|
+
gatewayFeeBps,
|
|
10
|
+
wordPaymentAtomic: `${wordPayment}`,
|
|
11
|
+
};
|
|
12
|
+
}
|
|
13
|
+
export function usageForWords(input) {
|
|
14
|
+
const gatewayFeeBps = input.gatewayFeeBps ?? 0;
|
|
15
|
+
const creatorAmount = input.pricePerWordAtomic * BigInt(input.wordsDelivered);
|
|
16
|
+
const total = addBasisPoints(creatorAmount, gatewayFeeBps);
|
|
17
|
+
return {
|
|
18
|
+
unit: "word",
|
|
19
|
+
wordsDelivered: input.wordsDelivered,
|
|
20
|
+
creatorAmountAtomic: `${creatorAmount}`,
|
|
21
|
+
rubiconFeeAtomic: `${total - creatorAmount}`,
|
|
22
|
+
totalPaidAtomic: `${total}`,
|
|
23
|
+
};
|
|
24
|
+
}
|
|
25
|
+
//# sourceMappingURL=pricing.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"pricing.js","sourceRoot":"","sources":["../src/pricing.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,EAAE,MAAM,YAAY,CAAC;AAmC5C,MAAM,UAAU,YAAY,CAAC,KAG5B;IACC,MAAM,aAAa,GAAG,KAAK,CAAC,aAAa,IAAI,CAAC,CAAC;IAC/C,MAAM,WAAW,GAAG,cAAc,CAAC,KAAK,CAAC,kBAAkB,EAAE,aAAa,CAAC,CAAC;IAC5E,OAAO;QACL,QAAQ,EAAE,MAAM;QAChB,YAAY,EAAE,MAAM;QACpB,kBAAkB,EAAE,GAAG,KAAK,CAAC,kBAAkB,EAAE;QACjD,aAAa;QACb,iBAAiB,EAAE,GAAG,WAAW,EAAE;KACpC,CAAC;AACJ,CAAC;AAED,MAAM,UAAU,aAAa,CAAC,KAI7B;IACC,MAAM,aAAa,GAAG,KAAK,CAAC,aAAa,IAAI,CAAC,CAAC;IAC/C,MAAM,aAAa,GAAG,KAAK,CAAC,kBAAkB,GAAG,MAAM,CAAC,KAAK,CAAC,cAAc,CAAC,CAAC;IAC9E,MAAM,KAAK,GAAG,cAAc,CAAC,aAAa,EAAE,aAAa,CAAC,CAAC;IAC3D,OAAO;QACL,IAAI,EAAE,MAAM;QACZ,cAAc,EAAE,KAAK,CAAC,cAAc;QACpC,mBAAmB,EAAE,GAAG,aAAa,EAAE;QACvC,gBAAgB,EAAE,GAAG,KAAK,GAAG,aAAa,EAAE;QAC5C,eAAe,EAAE,GAAG,KAAK,EAAE;KAC5B,CAAC;AACJ,CAAC"}
|
|
@@ -0,0 +1,218 @@
|
|
|
1
|
+
import type { AtomicAmount } from "./money.js";
|
|
2
|
+
import type { ArticleState } from "./contract.js";
|
|
3
|
+
import type { WordUsageReport } from "./pricing.js";
|
|
4
|
+
import type { SessionState } from "./session.js";
|
|
5
|
+
export interface Budget {
|
|
6
|
+
currency: "USDC";
|
|
7
|
+
maxAmountAtomic: AtomicAmount;
|
|
8
|
+
}
|
|
9
|
+
/** Safe public article metadata. Never includes unpaid body text. */
|
|
10
|
+
export interface ArticleSummary {
|
|
11
|
+
articleId: string;
|
|
12
|
+
creatorId: string;
|
|
13
|
+
creatorUsername: string;
|
|
14
|
+
title: string;
|
|
15
|
+
author: string;
|
|
16
|
+
state: ArticleState;
|
|
17
|
+
totalWords: number;
|
|
18
|
+
pricePerWordAtomic: AtomicAmount;
|
|
19
|
+
/** Maximum possible total price to read the whole article. */
|
|
20
|
+
maxArticlePriceAtomic: AtomicAmount;
|
|
21
|
+
sections: ArticleSectionSummary[];
|
|
22
|
+
}
|
|
23
|
+
/** Safe per-section navigation metadata. Headings only, no body content. */
|
|
24
|
+
export interface ArticleSectionSummary {
|
|
25
|
+
sectionId: string;
|
|
26
|
+
heading: string;
|
|
27
|
+
level: number;
|
|
28
|
+
wordStart: number;
|
|
29
|
+
wordCount: number;
|
|
30
|
+
}
|
|
31
|
+
export interface ArticleNavigation {
|
|
32
|
+
articleId: string;
|
|
33
|
+
sections: ArticleSectionSummary[];
|
|
34
|
+
/** Free, safe routing produced by the seller agent (no unpaid body text). */
|
|
35
|
+
sellerAgent: SellerNavigationSummary;
|
|
36
|
+
stopConditions: StreamStopCondition[];
|
|
37
|
+
}
|
|
38
|
+
export interface SellerNavigationSummary {
|
|
39
|
+
recommendedSectionId: string;
|
|
40
|
+
alternativeSectionIds: string[];
|
|
41
|
+
rationale: string;
|
|
42
|
+
safeHints: string[];
|
|
43
|
+
withheld: string[];
|
|
44
|
+
}
|
|
45
|
+
export interface StreamStopCondition {
|
|
46
|
+
kind: "max_words" | "max_payments" | "max_spend_atomic" | "sufficient_information" | "article_completed" | "payment_rejected";
|
|
47
|
+
description: string;
|
|
48
|
+
value?: string | number;
|
|
49
|
+
}
|
|
50
|
+
export interface StartConversationRequest {
|
|
51
|
+
articleId: string;
|
|
52
|
+
goal?: string;
|
|
53
|
+
message?: string;
|
|
54
|
+
}
|
|
55
|
+
export interface StartConversationResponse {
|
|
56
|
+
conversationId: string;
|
|
57
|
+
articleId: string;
|
|
58
|
+
article: ArticleSummary;
|
|
59
|
+
navigation: ArticleNavigation;
|
|
60
|
+
messages: ConversationMessage[];
|
|
61
|
+
}
|
|
62
|
+
export interface SendConversationMessageRequest {
|
|
63
|
+
message: string;
|
|
64
|
+
}
|
|
65
|
+
export interface SendConversationMessageResponse {
|
|
66
|
+
conversationId: string;
|
|
67
|
+
messages: ConversationMessage[];
|
|
68
|
+
recommendedSectionId?: string;
|
|
69
|
+
}
|
|
70
|
+
export interface ConversationMessage {
|
|
71
|
+
id: string;
|
|
72
|
+
role: "buyer" | "seller";
|
|
73
|
+
content: string;
|
|
74
|
+
recommendedSectionId?: string;
|
|
75
|
+
createdAt: string;
|
|
76
|
+
}
|
|
77
|
+
export interface StartSessionRequest {
|
|
78
|
+
articleId: string;
|
|
79
|
+
goal?: string;
|
|
80
|
+
conversationId?: string;
|
|
81
|
+
sectionId?: string;
|
|
82
|
+
budget: Budget;
|
|
83
|
+
metadata?: Record<string, unknown>;
|
|
84
|
+
}
|
|
85
|
+
export interface StartSessionResponse {
|
|
86
|
+
sessionId: string;
|
|
87
|
+
state: SessionState;
|
|
88
|
+
article: ArticleSummary;
|
|
89
|
+
navigation: ArticleNavigation;
|
|
90
|
+
/** Price the creator earns for one word. */
|
|
91
|
+
pricePerWordAtomic: AtomicAmount;
|
|
92
|
+
/** Maximum possible total price for the whole article. */
|
|
93
|
+
maxArticlePriceAtomic: AtomicAmount;
|
|
94
|
+
conversationId: string;
|
|
95
|
+
/** Exact amount the buyer pays to release one additional word. */
|
|
96
|
+
wordPaymentAtomic: AtomicAmount;
|
|
97
|
+
gatewayFeeBps: number;
|
|
98
|
+
/** x402 payment requirement for one word (when Circle/x402 is enabled). */
|
|
99
|
+
paymentRequired?: unknown;
|
|
100
|
+
expiresAt: string;
|
|
101
|
+
wordsPaid: number;
|
|
102
|
+
wordsDelivered: number;
|
|
103
|
+
paidAtomic: AtomicAmount;
|
|
104
|
+
}
|
|
105
|
+
/**
|
|
106
|
+
* One word-level payment. `idempotencyKey` ties a payment to a specific next
|
|
107
|
+
* word sequence so retries never release or charge for a word twice.
|
|
108
|
+
*/
|
|
109
|
+
export interface StreamPaymentRequest {
|
|
110
|
+
paymentPayload: unknown;
|
|
111
|
+
idempotencyKey?: string;
|
|
112
|
+
}
|
|
113
|
+
export interface StreamPaymentResponse {
|
|
114
|
+
accepted: boolean;
|
|
115
|
+
sequence: number;
|
|
116
|
+
word: string;
|
|
117
|
+
priceAtomic: AtomicAmount;
|
|
118
|
+
wordsPaid: number;
|
|
119
|
+
wordsDelivered: number;
|
|
120
|
+
paidAtomic: AtomicAmount;
|
|
121
|
+
completed: boolean;
|
|
122
|
+
/** Canonical, per-word settlement receipt. Present whenever a word is released. */
|
|
123
|
+
payment?: WordPaymentReceipt;
|
|
124
|
+
/** On-chain settlement transaction hash returned by the payment facilitator. */
|
|
125
|
+
transactionHash?: string;
|
|
126
|
+
/** All on-chain settlement transaction hashes for this payment, when available. */
|
|
127
|
+
transactionHashes?: string[];
|
|
128
|
+
/** Backwards-compatible alias for transactionHash. */
|
|
129
|
+
transferId?: string;
|
|
130
|
+
}
|
|
131
|
+
export interface WordPaymentReceipt {
|
|
132
|
+
paymentId: string;
|
|
133
|
+
sessionId: string;
|
|
134
|
+
articleId: string;
|
|
135
|
+
sequence: number;
|
|
136
|
+
meteringUnit: "word";
|
|
137
|
+
amountAtomic: AtomicAmount;
|
|
138
|
+
currency: "USDC";
|
|
139
|
+
network?: string;
|
|
140
|
+
payTo?: `0x${string}`;
|
|
141
|
+
transactionHash?: string;
|
|
142
|
+
transactionHashes?: string[];
|
|
143
|
+
/** Backwards-compatible alias for transactionHash. */
|
|
144
|
+
transferId?: string;
|
|
145
|
+
settledAt: string;
|
|
146
|
+
}
|
|
147
|
+
export interface PaymentVerification {
|
|
148
|
+
accepted: boolean;
|
|
149
|
+
transactionHash?: string;
|
|
150
|
+
transactionHashes?: string[];
|
|
151
|
+
transferId?: string;
|
|
152
|
+
network?: string;
|
|
153
|
+
payTo?: `0x${string}`;
|
|
154
|
+
amountAtomic?: AtomicAmount;
|
|
155
|
+
reason?: string;
|
|
156
|
+
}
|
|
157
|
+
export type GatewayEvent = {
|
|
158
|
+
type: "session.started";
|
|
159
|
+
sessionId: string;
|
|
160
|
+
articleId: string;
|
|
161
|
+
state: SessionState;
|
|
162
|
+
article: ArticleSummary;
|
|
163
|
+
pricePerWordAtomic: AtomicAmount;
|
|
164
|
+
wordPaymentAtomic: AtomicAmount;
|
|
165
|
+
} | {
|
|
166
|
+
type: "seller.message";
|
|
167
|
+
sessionId: string;
|
|
168
|
+
conversationId: string;
|
|
169
|
+
role: "seller";
|
|
170
|
+
message: string;
|
|
171
|
+
recommendedSectionId?: string;
|
|
172
|
+
} | {
|
|
173
|
+
type: "word.payment_accepted";
|
|
174
|
+
sessionId: string;
|
|
175
|
+
sequence: number;
|
|
176
|
+
paymentId: string;
|
|
177
|
+
amountAtomic: AtomicAmount;
|
|
178
|
+
network?: string;
|
|
179
|
+
payTo?: `0x${string}`;
|
|
180
|
+
transactionHash?: string;
|
|
181
|
+
transactionHashes?: string[];
|
|
182
|
+
transferId?: string;
|
|
183
|
+
} | {
|
|
184
|
+
type: "article.word";
|
|
185
|
+
sessionId: string;
|
|
186
|
+
articleId: string;
|
|
187
|
+
sequence: number;
|
|
188
|
+
word: string;
|
|
189
|
+
priceAtomic: AtomicAmount;
|
|
190
|
+
totalWordsStreamed: number;
|
|
191
|
+
totalPaidAtomic: AtomicAmount;
|
|
192
|
+
} | {
|
|
193
|
+
type: "article.usage";
|
|
194
|
+
sessionId: string;
|
|
195
|
+
usage: WordUsageReport;
|
|
196
|
+
wordsPaid: number;
|
|
197
|
+
wordsDelivered: number;
|
|
198
|
+
paidAtomic: AtomicAmount;
|
|
199
|
+
} | {
|
|
200
|
+
type: "article.completed";
|
|
201
|
+
sessionId: string;
|
|
202
|
+
articleId: string;
|
|
203
|
+
totalWordsStreamed: number;
|
|
204
|
+
totalPaidAtomic: AtomicAmount;
|
|
205
|
+
} | {
|
|
206
|
+
type: "article.error";
|
|
207
|
+
sessionId: string;
|
|
208
|
+
message: string;
|
|
209
|
+
} | {
|
|
210
|
+
type: "session.aborted";
|
|
211
|
+
sessionId: string;
|
|
212
|
+
reason: string;
|
|
213
|
+
} | {
|
|
214
|
+
type: "session.closed";
|
|
215
|
+
sessionId: string;
|
|
216
|
+
reason: string;
|
|
217
|
+
};
|
|
218
|
+
//# sourceMappingURL=protocol.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"protocol.d.ts","sourceRoot":"","sources":["../src/protocol.ts"],"names":[],"mappings":"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"}
|
package/dist/protocol.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"protocol.js","sourceRoot":"","sources":["../src/protocol.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import type { AtomicAmount } from "./money.js";
|
|
2
|
+
import type { Budget } from "./protocol.js";
|
|
3
|
+
export type SessionState = "open" | "active" | "completed" | "aborted" | "expired";
|
|
4
|
+
/**
|
|
5
|
+
* Runtime state for one budgeted reading session. All trusted values
|
|
6
|
+
* (pricePerWordAtomic, gatewayFeeBps, sellerWallet, creatorId) are loaded from
|
|
7
|
+
* persistent storage when the session is created — never from buyer input.
|
|
8
|
+
*/
|
|
9
|
+
export interface SessionRecord {
|
|
10
|
+
id: string;
|
|
11
|
+
articleId: string;
|
|
12
|
+
creatorId: string;
|
|
13
|
+
conversationId?: string;
|
|
14
|
+
goal?: string;
|
|
15
|
+
sectionId?: string;
|
|
16
|
+
budget: Budget;
|
|
17
|
+
/** Trusted price snapshot, copied from the stored article at session start. */
|
|
18
|
+
pricePerWordAtomic: bigint;
|
|
19
|
+
gatewayFeeBps: number;
|
|
20
|
+
/** Trusted settlement recipient, copied from the verified creator wallet. */
|
|
21
|
+
sellerWallet: `0x${string}`;
|
|
22
|
+
metadata: Record<string, unknown>;
|
|
23
|
+
state: SessionState;
|
|
24
|
+
/** Words the buyer has paid for (one payment === one word). */
|
|
25
|
+
wordsPaid: number;
|
|
26
|
+
/** Words actually delivered to the buyer. */
|
|
27
|
+
wordsDelivered: number;
|
|
28
|
+
/** Total atomic USDC the buyer has paid in this session. */
|
|
29
|
+
paidAtomic: bigint;
|
|
30
|
+
createdAt: Date;
|
|
31
|
+
updatedAt: Date;
|
|
32
|
+
expiresAt: Date;
|
|
33
|
+
}
|
|
34
|
+
export declare function createSession(input: {
|
|
35
|
+
id?: string;
|
|
36
|
+
articleId: string;
|
|
37
|
+
creatorId: string;
|
|
38
|
+
conversationId?: string;
|
|
39
|
+
goal?: string;
|
|
40
|
+
sectionId?: string;
|
|
41
|
+
budget: Budget;
|
|
42
|
+
pricePerWordAtomic: bigint;
|
|
43
|
+
gatewayFeeBps: number;
|
|
44
|
+
sellerWallet: `0x${string}`;
|
|
45
|
+
metadata?: Record<string, unknown>;
|
|
46
|
+
ttlMs: number;
|
|
47
|
+
}): SessionRecord;
|
|
48
|
+
/** Whether one more word payment fits inside the session budget. */
|
|
49
|
+
export declare function canAffordNextWord(session: SessionRecord, wordPaymentAtomic: bigint): boolean;
|
|
50
|
+
/** Record that one word's payment has been accepted. */
|
|
51
|
+
export declare function recordWordPayment(session: SessionRecord, amountAtomic: AtomicAmount): SessionRecord;
|
|
52
|
+
/** Record that one paid word has been delivered. */
|
|
53
|
+
export declare function recordWordDelivery(session: SessionRecord): SessionRecord;
|
|
54
|
+
export declare function isSessionExpired(session: SessionRecord, now?: Date): boolean;
|
|
55
|
+
//# sourceMappingURL=session.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"session.d.ts","sourceRoot":"","sources":["../src/session.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,YAAY,CAAC;AAC/C,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,eAAe,CAAC;AAE5C,MAAM,MAAM,YAAY,GAAG,MAAM,GAAG,QAAQ,GAAG,WAAW,GAAG,SAAS,GAAG,SAAS,CAAC;AAEnF;;;;GAIG;AACH,MAAM,WAAW,aAAa;IAC5B,EAAE,EAAE,MAAM,CAAC;IACX,SAAS,EAAE,MAAM,CAAC;IAClB,SAAS,EAAE,MAAM,CAAC;IAClB,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,MAAM,EAAE,MAAM,CAAC;IACf,+EAA+E;IAC/E,kBAAkB,EAAE,MAAM,CAAC;IAC3B,aAAa,EAAE,MAAM,CAAC;IACtB,6EAA6E;IAC7E,YAAY,EAAE,KAAK,MAAM,EAAE,CAAC;IAC5B,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAClC,KAAK,EAAE,YAAY,CAAC;IACpB,+DAA+D;IAC/D,SAAS,EAAE,MAAM,CAAC;IAClB,6CAA6C;IAC7C,cAAc,EAAE,MAAM,CAAC;IACvB,4DAA4D;IAC5D,UAAU,EAAE,MAAM,CAAC;IACnB,SAAS,EAAE,IAAI,CAAC;IAChB,SAAS,EAAE,IAAI,CAAC;IAChB,SAAS,EAAE,IAAI,CAAC;CACjB;AAED,wBAAgB,aAAa,CAAC,KAAK,EAAE;IACnC,EAAE,CAAC,EAAE,MAAM,CAAC;IACZ,SAAS,EAAE,MAAM,CAAC;IAClB,SAAS,EAAE,MAAM,CAAC;IAClB,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,MAAM,EAAE,MAAM,CAAC;IACf,kBAAkB,EAAE,MAAM,CAAC;IAC3B,aAAa,EAAE,MAAM,CAAC;IACtB,YAAY,EAAE,KAAK,MAAM,EAAE,CAAC;IAC5B,QAAQ,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IACnC,KAAK,EAAE,MAAM,CAAC;CACf,GAAG,aAAa,CAsBhB;AAED,oEAAoE;AACpE,wBAAgB,iBAAiB,CAAC,OAAO,EAAE,aAAa,EAAE,iBAAiB,EAAE,MAAM,GAAG,OAAO,CAE5F;AAED,wDAAwD;AACxD,wBAAgB,iBAAiB,CAAC,OAAO,EAAE,aAAa,EAAE,YAAY,EAAE,YAAY,GAAG,aAAa,CAQnG;AAED,oDAAoD;AACpD,wBAAgB,kBAAkB,CAAC,OAAO,EAAE,aAAa,GAAG,aAAa,CAIxE;AAED,wBAAgB,gBAAgB,CAAC,OAAO,EAAE,aAAa,EAAE,GAAG,OAAa,GAAG,OAAO,CAElF"}
|
package/dist/session.js
ADDED
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import { randomUUID } from "node:crypto";
|
|
2
|
+
export function createSession(input) {
|
|
3
|
+
const now = new Date();
|
|
4
|
+
return {
|
|
5
|
+
id: input.id ?? randomUUID(),
|
|
6
|
+
articleId: input.articleId,
|
|
7
|
+
creatorId: input.creatorId,
|
|
8
|
+
conversationId: input.conversationId,
|
|
9
|
+
goal: input.goal,
|
|
10
|
+
sectionId: input.sectionId,
|
|
11
|
+
budget: input.budget,
|
|
12
|
+
pricePerWordAtomic: input.pricePerWordAtomic,
|
|
13
|
+
gatewayFeeBps: input.gatewayFeeBps,
|
|
14
|
+
sellerWallet: input.sellerWallet,
|
|
15
|
+
metadata: input.metadata ?? {},
|
|
16
|
+
state: "open",
|
|
17
|
+
wordsPaid: 0,
|
|
18
|
+
wordsDelivered: 0,
|
|
19
|
+
paidAtomic: 0n,
|
|
20
|
+
createdAt: now,
|
|
21
|
+
updatedAt: now,
|
|
22
|
+
expiresAt: new Date(now.getTime() + input.ttlMs),
|
|
23
|
+
};
|
|
24
|
+
}
|
|
25
|
+
/** Whether one more word payment fits inside the session budget. */
|
|
26
|
+
export function canAffordNextWord(session, wordPaymentAtomic) {
|
|
27
|
+
return session.paidAtomic + wordPaymentAtomic <= BigInt(session.budget.maxAmountAtomic);
|
|
28
|
+
}
|
|
29
|
+
/** Record that one word's payment has been accepted. */
|
|
30
|
+
export function recordWordPayment(session, amountAtomic) {
|
|
31
|
+
session.paidAtomic += BigInt(amountAtomic);
|
|
32
|
+
session.wordsPaid += 1;
|
|
33
|
+
session.updatedAt = new Date();
|
|
34
|
+
if (session.state === "open") {
|
|
35
|
+
session.state = "active";
|
|
36
|
+
}
|
|
37
|
+
return session;
|
|
38
|
+
}
|
|
39
|
+
/** Record that one paid word has been delivered. */
|
|
40
|
+
export function recordWordDelivery(session) {
|
|
41
|
+
session.wordsDelivered += 1;
|
|
42
|
+
session.updatedAt = new Date();
|
|
43
|
+
return session;
|
|
44
|
+
}
|
|
45
|
+
export function isSessionExpired(session, now = new Date()) {
|
|
46
|
+
return session.expiresAt.getTime() <= now.getTime();
|
|
47
|
+
}
|
|
48
|
+
//# sourceMappingURL=session.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"session.js","sourceRoot":"","sources":["../src/session.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AAsCzC,MAAM,UAAU,aAAa,CAAC,KAa7B;IACC,MAAM,GAAG,GAAG,IAAI,IAAI,EAAE,CAAC;IACvB,OAAO;QACL,EAAE,EAAE,KAAK,CAAC,EAAE,IAAI,UAAU,EAAE;QAC5B,SAAS,EAAE,KAAK,CAAC,SAAS;QAC1B,SAAS,EAAE,KAAK,CAAC,SAAS;QAC1B,cAAc,EAAE,KAAK,CAAC,cAAc;QACpC,IAAI,EAAE,KAAK,CAAC,IAAI;QAChB,SAAS,EAAE,KAAK,CAAC,SAAS;QAC1B,MAAM,EAAE,KAAK,CAAC,MAAM;QACpB,kBAAkB,EAAE,KAAK,CAAC,kBAAkB;QAC5C,aAAa,EAAE,KAAK,CAAC,aAAa;QAClC,YAAY,EAAE,KAAK,CAAC,YAAY;QAChC,QAAQ,EAAE,KAAK,CAAC,QAAQ,IAAI,EAAE;QAC9B,KAAK,EAAE,MAAM;QACb,SAAS,EAAE,CAAC;QACZ,cAAc,EAAE,CAAC;QACjB,UAAU,EAAE,EAAE;QACd,SAAS,EAAE,GAAG;QACd,SAAS,EAAE,GAAG;QACd,SAAS,EAAE,IAAI,IAAI,CAAC,GAAG,CAAC,OAAO,EAAE,GAAG,KAAK,CAAC,KAAK,CAAC;KACjD,CAAC;AACJ,CAAC;AAED,oEAAoE;AACpE,MAAM,UAAU,iBAAiB,CAAC,OAAsB,EAAE,iBAAyB;IACjF,OAAO,OAAO,CAAC,UAAU,GAAG,iBAAiB,IAAI,MAAM,CAAC,OAAO,CAAC,MAAM,CAAC,eAAe,CAAC,CAAC;AAC1F,CAAC;AAED,wDAAwD;AACxD,MAAM,UAAU,iBAAiB,CAAC,OAAsB,EAAE,YAA0B;IAClF,OAAO,CAAC,UAAU,IAAI,MAAM,CAAC,YAAY,CAAC,CAAC;IAC3C,OAAO,CAAC,SAAS,IAAI,CAAC,CAAC;IACvB,OAAO,CAAC,SAAS,GAAG,IAAI,IAAI,EAAE,CAAC;IAC/B,IAAI,OAAO,CAAC,KAAK,KAAK,MAAM,EAAE,CAAC;QAC7B,OAAO,CAAC,KAAK,GAAG,QAAQ,CAAC;IAC3B,CAAC;IACD,OAAO,OAAO,CAAC;AACjB,CAAC;AAED,oDAAoD;AACpD,MAAM,UAAU,kBAAkB,CAAC,OAAsB;IACvD,OAAO,CAAC,cAAc,IAAI,CAAC,CAAC;IAC5B,OAAO,CAAC,SAAS,GAAG,IAAI,IAAI,EAAE,CAAC;IAC/B,OAAO,OAAO,CAAC;AACjB,CAAC;AAED,MAAM,UAAU,gBAAgB,CAAC,OAAsB,EAAE,GAAG,GAAG,IAAI,IAAI,EAAE;IACvE,OAAO,OAAO,CAAC,SAAS,CAAC,OAAO,EAAE,IAAI,GAAG,CAAC,OAAO,EAAE,CAAC;AACtD,CAAC"}
|
package/package.json
ADDED
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@rubicon-caliga/core",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Shared protocol types, pricing math, and session primitives for the Rubicon x402 streaming gateway.",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"type": "module",
|
|
7
|
+
"main": "dist/index.js",
|
|
8
|
+
"types": "dist/index.d.ts",
|
|
9
|
+
"exports": {
|
|
10
|
+
".": {
|
|
11
|
+
"types": "./dist/index.d.ts",
|
|
12
|
+
"import": "./dist/index.js"
|
|
13
|
+
}
|
|
14
|
+
},
|
|
15
|
+
"files": [
|
|
16
|
+
"dist",
|
|
17
|
+
"src"
|
|
18
|
+
],
|
|
19
|
+
"repository": {
|
|
20
|
+
"type": "git",
|
|
21
|
+
"url": "git+https://github.com/michaelzoub/rubicon.git",
|
|
22
|
+
"directory": "packages/core"
|
|
23
|
+
},
|
|
24
|
+
"publishConfig": {
|
|
25
|
+
"access": "public"
|
|
26
|
+
},
|
|
27
|
+
"scripts": {
|
|
28
|
+
"build": "tsc -p tsconfig.json",
|
|
29
|
+
"dev": "tsx src/index.ts",
|
|
30
|
+
"lint": "tsc -p tsconfig.json --noEmit",
|
|
31
|
+
"typecheck": "tsc -p tsconfig.json --noEmit",
|
|
32
|
+
"test": "node --test dist/**/*.test.js",
|
|
33
|
+
"prepublishOnly": "pnpm run build"
|
|
34
|
+
}
|
|
35
|
+
}
|
package/src/contract.ts
ADDED
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
import type { AtomicAmount } from "./money.js";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Shared API contract between Rubicon (this gateway) and the rubicon-marketing
|
|
5
|
+
* Next.js application. Both repositories agree on these shapes, the article
|
|
6
|
+
* states, pricing units (atomic USDC, 1 USDC = 1_000_000), word-counting rules,
|
|
7
|
+
* wallet format, creator ownership, and seller-agent configuration.
|
|
8
|
+
*
|
|
9
|
+
* These types describe the shared persistent data model. The marketing app owns
|
|
10
|
+
* creator authentication and creator-facing CRUD; Rubicon reads published data
|
|
11
|
+
* and writes runtime read/earnings activity.
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Lifecycle of an article. Only `live` articles are consumable by buyer agents
|
|
16
|
+
* or visible in the public repository.
|
|
17
|
+
*/
|
|
18
|
+
export type ArticleState = "draft" | "live" | "paused" | "archived" | "deleted";
|
|
19
|
+
|
|
20
|
+
export const PUBLIC_ARTICLE_STATE: ArticleState = "live";
|
|
21
|
+
|
|
22
|
+
export type WalletNetwork = string; // CAIP-2 string, e.g. "eip155:5042002"
|
|
23
|
+
|
|
24
|
+
export interface Creator {
|
|
25
|
+
id: string;
|
|
26
|
+
username: string;
|
|
27
|
+
displayName: string;
|
|
28
|
+
createdAt: string;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export interface CreatorProfile {
|
|
32
|
+
creatorId: string;
|
|
33
|
+
bio?: string;
|
|
34
|
+
avatarUrl?: string;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export interface CreatorWallet {
|
|
38
|
+
creatorId: string;
|
|
39
|
+
/** Verified receiving wallet. Settlement always pays this address. */
|
|
40
|
+
address: `0x${string}`;
|
|
41
|
+
network: WalletNetwork;
|
|
42
|
+
verified: boolean;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
export interface ArticleSection {
|
|
46
|
+
id: string;
|
|
47
|
+
articleId: string;
|
|
48
|
+
/** Stable slug used by buyer agents for navigation. */
|
|
49
|
+
sectionId: string;
|
|
50
|
+
heading: string;
|
|
51
|
+
level: number;
|
|
52
|
+
/** Zero-based word index where this section's body begins. */
|
|
53
|
+
wordStart: number;
|
|
54
|
+
wordCount: number;
|
|
55
|
+
ordinal: number;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Optional per-article seller-agent configuration. Agreed between Rubicon and
|
|
60
|
+
* rubicon-marketing; absent values fall back to gateway defaults.
|
|
61
|
+
*/
|
|
62
|
+
export interface SellerAgentConfig {
|
|
63
|
+
persona?: string;
|
|
64
|
+
/** Identifier of a configured model/provider, e.g. "anthropic:claude-opus-4-8". */
|
|
65
|
+
model?: string;
|
|
66
|
+
/** Extra navigation guidance the creator wants the seller agent to follow. */
|
|
67
|
+
guidance?: string;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
export interface Article {
|
|
71
|
+
id: string;
|
|
72
|
+
creatorId: string;
|
|
73
|
+
title: string;
|
|
74
|
+
author: string;
|
|
75
|
+
state: ArticleState;
|
|
76
|
+
pricePerWordAtomic: AtomicAmount;
|
|
77
|
+
/** Optional creator-set cap on the total price of the article. */
|
|
78
|
+
maxArticlePriceAtomic?: AtomicAmount;
|
|
79
|
+
totalWords: number;
|
|
80
|
+
revision: number;
|
|
81
|
+
sellerAgentConfig?: SellerAgentConfig;
|
|
82
|
+
createdAt: string;
|
|
83
|
+
updatedAt: string;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
export interface EarningsSummary {
|
|
87
|
+
creatorId: string;
|
|
88
|
+
articleId?: string;
|
|
89
|
+
wordsDelivered: number;
|
|
90
|
+
creatorAmountAtomic: AtomicAmount;
|
|
91
|
+
rubiconFeeAtomic: AtomicAmount;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
export interface PaymentActivity {
|
|
95
|
+
paymentId: string;
|
|
96
|
+
sessionId: string;
|
|
97
|
+
articleId: string;
|
|
98
|
+
sequence: number;
|
|
99
|
+
amountAtomic: AtomicAmount;
|
|
100
|
+
creatorAmountAtomic: AtomicAmount;
|
|
101
|
+
rubiconFeeAtomic: AtomicAmount;
|
|
102
|
+
network?: string;
|
|
103
|
+
payTo?: `0x${string}`;
|
|
104
|
+
transactionHash?: string;
|
|
105
|
+
transactionHashes?: string[];
|
|
106
|
+
transferId?: string;
|
|
107
|
+
createdAt: string;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
export interface SellerAgentMessageRecord {
|
|
111
|
+
id: string;
|
|
112
|
+
conversationId: string;
|
|
113
|
+
sessionId?: string;
|
|
114
|
+
articleId: string;
|
|
115
|
+
role: "buyer" | "seller";
|
|
116
|
+
content: string;
|
|
117
|
+
createdAt: string;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
export interface StreamSessionRecord {
|
|
121
|
+
id: string;
|
|
122
|
+
articleId: string;
|
|
123
|
+
creatorId: string;
|
|
124
|
+
conversationId?: string;
|
|
125
|
+
state: string;
|
|
126
|
+
wordsPaid: number;
|
|
127
|
+
wordsDelivered: number;
|
|
128
|
+
paidAtomic: AtomicAmount;
|
|
129
|
+
budgetAtomic: AtomicAmount;
|
|
130
|
+
createdAt: string;
|
|
131
|
+
expiresAt: string;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
export interface WordDeliveryRecord {
|
|
135
|
+
sessionId: string;
|
|
136
|
+
articleId: string;
|
|
137
|
+
sequence: number;
|
|
138
|
+
word: string;
|
|
139
|
+
priceAtomic: AtomicAmount;
|
|
140
|
+
paymentId: string;
|
|
141
|
+
createdAt: string;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
export interface ApiError {
|
|
145
|
+
error: string;
|
|
146
|
+
message?: string;
|
|
147
|
+
details?: Record<string, unknown>;
|
|
148
|
+
}
|
package/src/index.ts
ADDED
package/src/money.ts
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
export const USDC_ATOMIC_UNITS = 1_000_000n;
|
|
2
|
+
|
|
3
|
+
export type AtomicAmount = `${bigint}`;
|
|
4
|
+
|
|
5
|
+
export function parseUsdcToAtomic(value: string): bigint {
|
|
6
|
+
const [whole = "0", fraction = ""] = value.split(".");
|
|
7
|
+
const paddedFraction = `${fraction}000000`.slice(0, 6);
|
|
8
|
+
return BigInt(whole) * USDC_ATOMIC_UNITS + BigInt(paddedFraction);
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export function formatAtomicUsdc(amount: bigint): string {
|
|
12
|
+
const whole = amount / USDC_ATOMIC_UNITS;
|
|
13
|
+
const fraction = `${amount % USDC_ATOMIC_UNITS}`.padStart(6, "0").replace(/0+$/, "");
|
|
14
|
+
return fraction.length > 0 ? `${whole}.${fraction}` : `${whole}`;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export function addBasisPoints(amount: bigint, basisPoints: number): bigint {
|
|
18
|
+
return amount + (amount * BigInt(basisPoints)) / 10_000n;
|
|
19
|
+
}
|
package/src/pricing.ts
ADDED
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import { addBasisPoints } from "./money.js";
|
|
2
|
+
|
|
3
|
+
export type MeteringUnit = "word";
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* The atomic content unit in Rubicon is exactly one word. A quote describes the
|
|
7
|
+
* price the creator earns for a single word and the amount the buyer must pay to
|
|
8
|
+
* release one additional word.
|
|
9
|
+
*
|
|
10
|
+
* Rubicon never bills in chunks. Circle/x402 may batch settlement internally,
|
|
11
|
+
* but the application-level accounting is always one word = one paid unit.
|
|
12
|
+
*/
|
|
13
|
+
export interface WordPriceQuote {
|
|
14
|
+
currency: "USDC";
|
|
15
|
+
meteringUnit: MeteringUnit;
|
|
16
|
+
/** Price the creator earns for one delivered word. */
|
|
17
|
+
pricePerWordAtomic: `${bigint}`;
|
|
18
|
+
/** Rubicon gateway fee in basis points. Defaults to 0. */
|
|
19
|
+
gatewayFeeBps: number;
|
|
20
|
+
/** Exact amount the buyer authorizes/sends to release one additional word. */
|
|
21
|
+
wordPaymentAtomic: `${bigint}`;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export interface WordUsageReport {
|
|
25
|
+
unit: MeteringUnit;
|
|
26
|
+
/** Number of individually delivered, individually paid words. */
|
|
27
|
+
wordsDelivered: number;
|
|
28
|
+
/** Full word price accruing to the creator (no Rubicon fee deducted). */
|
|
29
|
+
creatorAmountAtomic: `${bigint}`;
|
|
30
|
+
/** Rubicon fee. Zero by default — creators keep the full word price. */
|
|
31
|
+
rubiconFeeAtomic: `${bigint}`;
|
|
32
|
+
/** Total atomic USDC the buyer paid for these words. */
|
|
33
|
+
totalPaidAtomic: `${bigint}`;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export function quotePerWord(input: {
|
|
37
|
+
pricePerWordAtomic: bigint;
|
|
38
|
+
gatewayFeeBps?: number;
|
|
39
|
+
}): WordPriceQuote {
|
|
40
|
+
const gatewayFeeBps = input.gatewayFeeBps ?? 0;
|
|
41
|
+
const wordPayment = addBasisPoints(input.pricePerWordAtomic, gatewayFeeBps);
|
|
42
|
+
return {
|
|
43
|
+
currency: "USDC",
|
|
44
|
+
meteringUnit: "word",
|
|
45
|
+
pricePerWordAtomic: `${input.pricePerWordAtomic}`,
|
|
46
|
+
gatewayFeeBps,
|
|
47
|
+
wordPaymentAtomic: `${wordPayment}`,
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
export function usageForWords(input: {
|
|
52
|
+
wordsDelivered: number;
|
|
53
|
+
pricePerWordAtomic: bigint;
|
|
54
|
+
gatewayFeeBps?: number;
|
|
55
|
+
}): WordUsageReport {
|
|
56
|
+
const gatewayFeeBps = input.gatewayFeeBps ?? 0;
|
|
57
|
+
const creatorAmount = input.pricePerWordAtomic * BigInt(input.wordsDelivered);
|
|
58
|
+
const total = addBasisPoints(creatorAmount, gatewayFeeBps);
|
|
59
|
+
return {
|
|
60
|
+
unit: "word",
|
|
61
|
+
wordsDelivered: input.wordsDelivered,
|
|
62
|
+
creatorAmountAtomic: `${creatorAmount}`,
|
|
63
|
+
rubiconFeeAtomic: `${total - creatorAmount}`,
|
|
64
|
+
totalPaidAtomic: `${total}`,
|
|
65
|
+
};
|
|
66
|
+
}
|
package/src/protocol.ts
ADDED
|
@@ -0,0 +1,250 @@
|
|
|
1
|
+
import type { AtomicAmount } from "./money.js";
|
|
2
|
+
import type { ArticleState } from "./contract.js";
|
|
3
|
+
import type { WordUsageReport } from "./pricing.js";
|
|
4
|
+
import type { SessionState } from "./session.js";
|
|
5
|
+
|
|
6
|
+
export interface Budget {
|
|
7
|
+
currency: "USDC";
|
|
8
|
+
maxAmountAtomic: AtomicAmount;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
/** Safe public article metadata. Never includes unpaid body text. */
|
|
12
|
+
export interface ArticleSummary {
|
|
13
|
+
articleId: string;
|
|
14
|
+
creatorId: string;
|
|
15
|
+
creatorUsername: string;
|
|
16
|
+
title: string;
|
|
17
|
+
author: string;
|
|
18
|
+
state: ArticleState;
|
|
19
|
+
totalWords: number;
|
|
20
|
+
pricePerWordAtomic: AtomicAmount;
|
|
21
|
+
/** Maximum possible total price to read the whole article. */
|
|
22
|
+
maxArticlePriceAtomic: AtomicAmount;
|
|
23
|
+
sections: ArticleSectionSummary[];
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/** Safe per-section navigation metadata. Headings only, no body content. */
|
|
27
|
+
export interface ArticleSectionSummary {
|
|
28
|
+
sectionId: string;
|
|
29
|
+
heading: string;
|
|
30
|
+
level: number;
|
|
31
|
+
wordStart: number;
|
|
32
|
+
wordCount: number;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export interface ArticleNavigation {
|
|
36
|
+
articleId: string;
|
|
37
|
+
sections: ArticleSectionSummary[];
|
|
38
|
+
/** Free, safe routing produced by the seller agent (no unpaid body text). */
|
|
39
|
+
sellerAgent: SellerNavigationSummary;
|
|
40
|
+
stopConditions: StreamStopCondition[];
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export interface SellerNavigationSummary {
|
|
44
|
+
recommendedSectionId: string;
|
|
45
|
+
alternativeSectionIds: string[];
|
|
46
|
+
rationale: string;
|
|
47
|
+
safeHints: string[];
|
|
48
|
+
withheld: string[];
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
export interface StreamStopCondition {
|
|
52
|
+
kind:
|
|
53
|
+
| "max_words"
|
|
54
|
+
| "max_payments"
|
|
55
|
+
| "max_spend_atomic"
|
|
56
|
+
| "sufficient_information"
|
|
57
|
+
| "article_completed"
|
|
58
|
+
| "payment_rejected";
|
|
59
|
+
description: string;
|
|
60
|
+
value?: string | number;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
// ---------------------------------------------------------------------------
|
|
64
|
+
// Seller-agent conversations
|
|
65
|
+
// ---------------------------------------------------------------------------
|
|
66
|
+
|
|
67
|
+
export interface StartConversationRequest {
|
|
68
|
+
articleId: string;
|
|
69
|
+
goal?: string;
|
|
70
|
+
message?: string;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
export interface StartConversationResponse {
|
|
74
|
+
conversationId: string;
|
|
75
|
+
articleId: string;
|
|
76
|
+
article: ArticleSummary;
|
|
77
|
+
navigation: ArticleNavigation;
|
|
78
|
+
messages: ConversationMessage[];
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
export interface SendConversationMessageRequest {
|
|
82
|
+
message: string;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
export interface SendConversationMessageResponse {
|
|
86
|
+
conversationId: string;
|
|
87
|
+
messages: ConversationMessage[];
|
|
88
|
+
recommendedSectionId?: string;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
export interface ConversationMessage {
|
|
92
|
+
id: string;
|
|
93
|
+
role: "buyer" | "seller";
|
|
94
|
+
content: string;
|
|
95
|
+
recommendedSectionId?: string;
|
|
96
|
+
createdAt: string;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
// ---------------------------------------------------------------------------
|
|
100
|
+
// Sessions
|
|
101
|
+
// ---------------------------------------------------------------------------
|
|
102
|
+
|
|
103
|
+
export interface StartSessionRequest {
|
|
104
|
+
articleId: string;
|
|
105
|
+
goal?: string;
|
|
106
|
+
conversationId?: string;
|
|
107
|
+
sectionId?: string;
|
|
108
|
+
budget: Budget;
|
|
109
|
+
metadata?: Record<string, unknown>;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
export interface StartSessionResponse {
|
|
113
|
+
sessionId: string;
|
|
114
|
+
state: SessionState;
|
|
115
|
+
article: ArticleSummary;
|
|
116
|
+
navigation: ArticleNavigation;
|
|
117
|
+
/** Price the creator earns for one word. */
|
|
118
|
+
pricePerWordAtomic: AtomicAmount;
|
|
119
|
+
/** Maximum possible total price for the whole article. */
|
|
120
|
+
maxArticlePriceAtomic: AtomicAmount;
|
|
121
|
+
conversationId: string;
|
|
122
|
+
/** Exact amount the buyer pays to release one additional word. */
|
|
123
|
+
wordPaymentAtomic: AtomicAmount;
|
|
124
|
+
gatewayFeeBps: number;
|
|
125
|
+
/** x402 payment requirement for one word (when Circle/x402 is enabled). */
|
|
126
|
+
paymentRequired?: unknown;
|
|
127
|
+
expiresAt: string;
|
|
128
|
+
wordsPaid: number;
|
|
129
|
+
wordsDelivered: number;
|
|
130
|
+
paidAtomic: AtomicAmount;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
/**
|
|
134
|
+
* One word-level payment. `idempotencyKey` ties a payment to a specific next
|
|
135
|
+
* word sequence so retries never release or charge for a word twice.
|
|
136
|
+
*/
|
|
137
|
+
export interface StreamPaymentRequest {
|
|
138
|
+
paymentPayload: unknown;
|
|
139
|
+
idempotencyKey?: string;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
export interface StreamPaymentResponse {
|
|
143
|
+
accepted: boolean;
|
|
144
|
+
sequence: number;
|
|
145
|
+
word: string;
|
|
146
|
+
priceAtomic: AtomicAmount;
|
|
147
|
+
wordsPaid: number;
|
|
148
|
+
wordsDelivered: number;
|
|
149
|
+
paidAtomic: AtomicAmount;
|
|
150
|
+
completed: boolean;
|
|
151
|
+
/** Canonical, per-word settlement receipt. Present whenever a word is released. */
|
|
152
|
+
payment?: WordPaymentReceipt;
|
|
153
|
+
/** On-chain settlement transaction hash returned by the payment facilitator. */
|
|
154
|
+
transactionHash?: string;
|
|
155
|
+
/** All on-chain settlement transaction hashes for this payment, when available. */
|
|
156
|
+
transactionHashes?: string[];
|
|
157
|
+
/** Backwards-compatible alias for transactionHash. */
|
|
158
|
+
transferId?: string;
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
export interface WordPaymentReceipt {
|
|
162
|
+
paymentId: string;
|
|
163
|
+
sessionId: string;
|
|
164
|
+
articleId: string;
|
|
165
|
+
sequence: number;
|
|
166
|
+
meteringUnit: "word";
|
|
167
|
+
amountAtomic: AtomicAmount;
|
|
168
|
+
currency: "USDC";
|
|
169
|
+
network?: string;
|
|
170
|
+
payTo?: `0x${string}`;
|
|
171
|
+
transactionHash?: string;
|
|
172
|
+
transactionHashes?: string[];
|
|
173
|
+
/** Backwards-compatible alias for transactionHash. */
|
|
174
|
+
transferId?: string;
|
|
175
|
+
settledAt: string;
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
export interface PaymentVerification {
|
|
179
|
+
accepted: boolean;
|
|
180
|
+
transactionHash?: string;
|
|
181
|
+
transactionHashes?: string[];
|
|
182
|
+
transferId?: string;
|
|
183
|
+
network?: string;
|
|
184
|
+
payTo?: `0x${string}`;
|
|
185
|
+
amountAtomic?: AtomicAmount;
|
|
186
|
+
reason?: string;
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
// ---------------------------------------------------------------------------
|
|
190
|
+
// Events
|
|
191
|
+
// ---------------------------------------------------------------------------
|
|
192
|
+
|
|
193
|
+
export type GatewayEvent =
|
|
194
|
+
| {
|
|
195
|
+
type: "session.started";
|
|
196
|
+
sessionId: string;
|
|
197
|
+
articleId: string;
|
|
198
|
+
state: SessionState;
|
|
199
|
+
article: ArticleSummary;
|
|
200
|
+
pricePerWordAtomic: AtomicAmount;
|
|
201
|
+
wordPaymentAtomic: AtomicAmount;
|
|
202
|
+
}
|
|
203
|
+
| {
|
|
204
|
+
type: "seller.message";
|
|
205
|
+
sessionId: string;
|
|
206
|
+
conversationId: string;
|
|
207
|
+
role: "seller";
|
|
208
|
+
message: string;
|
|
209
|
+
recommendedSectionId?: string;
|
|
210
|
+
}
|
|
211
|
+
| {
|
|
212
|
+
type: "word.payment_accepted";
|
|
213
|
+
sessionId: string;
|
|
214
|
+
sequence: number;
|
|
215
|
+
paymentId: string;
|
|
216
|
+
amountAtomic: AtomicAmount;
|
|
217
|
+
network?: string;
|
|
218
|
+
payTo?: `0x${string}`;
|
|
219
|
+
transactionHash?: string;
|
|
220
|
+
transactionHashes?: string[];
|
|
221
|
+
transferId?: string;
|
|
222
|
+
}
|
|
223
|
+
| {
|
|
224
|
+
type: "article.word";
|
|
225
|
+
sessionId: string;
|
|
226
|
+
articleId: string;
|
|
227
|
+
sequence: number;
|
|
228
|
+
word: string;
|
|
229
|
+
priceAtomic: AtomicAmount;
|
|
230
|
+
totalWordsStreamed: number;
|
|
231
|
+
totalPaidAtomic: AtomicAmount;
|
|
232
|
+
}
|
|
233
|
+
| {
|
|
234
|
+
type: "article.usage";
|
|
235
|
+
sessionId: string;
|
|
236
|
+
usage: WordUsageReport;
|
|
237
|
+
wordsPaid: number;
|
|
238
|
+
wordsDelivered: number;
|
|
239
|
+
paidAtomic: AtomicAmount;
|
|
240
|
+
}
|
|
241
|
+
| {
|
|
242
|
+
type: "article.completed";
|
|
243
|
+
sessionId: string;
|
|
244
|
+
articleId: string;
|
|
245
|
+
totalWordsStreamed: number;
|
|
246
|
+
totalPaidAtomic: AtomicAmount;
|
|
247
|
+
}
|
|
248
|
+
| { type: "article.error"; sessionId: string; message: string }
|
|
249
|
+
| { type: "session.aborted"; sessionId: string; reason: string }
|
|
250
|
+
| { type: "session.closed"; sessionId: string; reason: string };
|
package/src/session.ts
ADDED
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
import { randomUUID } from "node:crypto";
|
|
2
|
+
|
|
3
|
+
import type { AtomicAmount } from "./money.js";
|
|
4
|
+
import type { Budget } from "./protocol.js";
|
|
5
|
+
|
|
6
|
+
export type SessionState = "open" | "active" | "completed" | "aborted" | "expired";
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Runtime state for one budgeted reading session. All trusted values
|
|
10
|
+
* (pricePerWordAtomic, gatewayFeeBps, sellerWallet, creatorId) are loaded from
|
|
11
|
+
* persistent storage when the session is created — never from buyer input.
|
|
12
|
+
*/
|
|
13
|
+
export interface SessionRecord {
|
|
14
|
+
id: string;
|
|
15
|
+
articleId: string;
|
|
16
|
+
creatorId: string;
|
|
17
|
+
conversationId?: string;
|
|
18
|
+
goal?: string;
|
|
19
|
+
sectionId?: string;
|
|
20
|
+
budget: Budget;
|
|
21
|
+
/** Trusted price snapshot, copied from the stored article at session start. */
|
|
22
|
+
pricePerWordAtomic: bigint;
|
|
23
|
+
gatewayFeeBps: number;
|
|
24
|
+
/** Trusted settlement recipient, copied from the verified creator wallet. */
|
|
25
|
+
sellerWallet: `0x${string}`;
|
|
26
|
+
metadata: Record<string, unknown>;
|
|
27
|
+
state: SessionState;
|
|
28
|
+
/** Words the buyer has paid for (one payment === one word). */
|
|
29
|
+
wordsPaid: number;
|
|
30
|
+
/** Words actually delivered to the buyer. */
|
|
31
|
+
wordsDelivered: number;
|
|
32
|
+
/** Total atomic USDC the buyer has paid in this session. */
|
|
33
|
+
paidAtomic: bigint;
|
|
34
|
+
createdAt: Date;
|
|
35
|
+
updatedAt: Date;
|
|
36
|
+
expiresAt: Date;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export function createSession(input: {
|
|
40
|
+
id?: string;
|
|
41
|
+
articleId: string;
|
|
42
|
+
creatorId: string;
|
|
43
|
+
conversationId?: string;
|
|
44
|
+
goal?: string;
|
|
45
|
+
sectionId?: string;
|
|
46
|
+
budget: Budget;
|
|
47
|
+
pricePerWordAtomic: bigint;
|
|
48
|
+
gatewayFeeBps: number;
|
|
49
|
+
sellerWallet: `0x${string}`;
|
|
50
|
+
metadata?: Record<string, unknown>;
|
|
51
|
+
ttlMs: number;
|
|
52
|
+
}): SessionRecord {
|
|
53
|
+
const now = new Date();
|
|
54
|
+
return {
|
|
55
|
+
id: input.id ?? randomUUID(),
|
|
56
|
+
articleId: input.articleId,
|
|
57
|
+
creatorId: input.creatorId,
|
|
58
|
+
conversationId: input.conversationId,
|
|
59
|
+
goal: input.goal,
|
|
60
|
+
sectionId: input.sectionId,
|
|
61
|
+
budget: input.budget,
|
|
62
|
+
pricePerWordAtomic: input.pricePerWordAtomic,
|
|
63
|
+
gatewayFeeBps: input.gatewayFeeBps,
|
|
64
|
+
sellerWallet: input.sellerWallet,
|
|
65
|
+
metadata: input.metadata ?? {},
|
|
66
|
+
state: "open",
|
|
67
|
+
wordsPaid: 0,
|
|
68
|
+
wordsDelivered: 0,
|
|
69
|
+
paidAtomic: 0n,
|
|
70
|
+
createdAt: now,
|
|
71
|
+
updatedAt: now,
|
|
72
|
+
expiresAt: new Date(now.getTime() + input.ttlMs),
|
|
73
|
+
};
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/** Whether one more word payment fits inside the session budget. */
|
|
77
|
+
export function canAffordNextWord(session: SessionRecord, wordPaymentAtomic: bigint): boolean {
|
|
78
|
+
return session.paidAtomic + wordPaymentAtomic <= BigInt(session.budget.maxAmountAtomic);
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/** Record that one word's payment has been accepted. */
|
|
82
|
+
export function recordWordPayment(session: SessionRecord, amountAtomic: AtomicAmount): SessionRecord {
|
|
83
|
+
session.paidAtomic += BigInt(amountAtomic);
|
|
84
|
+
session.wordsPaid += 1;
|
|
85
|
+
session.updatedAt = new Date();
|
|
86
|
+
if (session.state === "open") {
|
|
87
|
+
session.state = "active";
|
|
88
|
+
}
|
|
89
|
+
return session;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/** Record that one paid word has been delivered. */
|
|
93
|
+
export function recordWordDelivery(session: SessionRecord): SessionRecord {
|
|
94
|
+
session.wordsDelivered += 1;
|
|
95
|
+
session.updatedAt = new Date();
|
|
96
|
+
return session;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
export function isSessionExpired(session: SessionRecord, now = new Date()): boolean {
|
|
100
|
+
return session.expiresAt.getTime() <= now.getTime();
|
|
101
|
+
}
|