@rubicon-caliga/agent-sdk 0.1.1 → 0.1.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +42 -25
- package/dist/agent-client.d.ts +20 -5
- package/dist/agent-client.d.ts.map +1 -1
- package/dist/agent-client.js +82 -4
- package/dist/agent-client.js.map +1 -1
- package/dist/agent-client.test.d.ts +2 -0
- package/dist/agent-client.test.d.ts.map +1 -0
- package/dist/agent-client.test.js +191 -0
- package/dist/agent-client.test.js.map +1 -0
- package/dist/circle-agent-wallet.d.ts +6 -6
- package/dist/circle-agent-wallet.d.ts.map +1 -1
- package/dist/circle-agent-wallet.js +8 -8
- package/dist/circle-agent-wallet.js.map +1 -1
- package/dist/circle-cli-gateway-payment.d.ts +51 -4
- package/dist/circle-cli-gateway-payment.d.ts.map +1 -1
- package/dist/circle-cli-gateway-payment.js +85 -14
- package/dist/circle-cli-gateway-payment.js.map +1 -1
- package/dist/circle-cli-gateway-payment.test.js +110 -1
- package/dist/circle-cli-gateway-payment.test.js.map +1 -1
- package/dist/payment-engine.d.ts +17 -5
- package/dist/payment-engine.d.ts.map +1 -1
- package/dist/payment-engine.js +30 -2
- package/dist/payment-engine.js.map +1 -1
- package/package.json +2 -2
- package/src/agent-client.test.ts +205 -0
- package/src/agent-client.ts +93 -4
- package/src/circle-agent-wallet.ts +10 -12
- package/src/circle-cli-gateway-payment.test.ts +129 -0
- package/src/circle-cli-gateway-payment.ts +114 -15
- package/src/payment-engine.ts +39 -5
|
@@ -15,8 +15,24 @@ export interface CircleCliGatewayPaymentEngineOptions {
|
|
|
15
15
|
/**
|
|
16
16
|
* Agent Wallet address controlled by Circle CLI. When omitted, the engine
|
|
17
17
|
* resolves the sole agent wallet returned by `circle wallet list`.
|
|
18
|
+
*
|
|
19
|
+
* @deprecated Use `agentWalletAddress`. This alias is kept for existing SDK
|
|
20
|
+
* callers and is treated as the Circle CLI signing wallet, not the x402
|
|
21
|
+
* authorization `from` address.
|
|
18
22
|
*/
|
|
19
23
|
walletAddress?: `0x${string}`;
|
|
24
|
+
/**
|
|
25
|
+
* Agent Wallet address controlled by Circle CLI. This is passed to
|
|
26
|
+
* `circle wallet sign typed-data --address`.
|
|
27
|
+
*/
|
|
28
|
+
agentWalletAddress?: `0x${string}`;
|
|
29
|
+
/**
|
|
30
|
+
* Gateway backing EOA used as the x402 authorization `from` address. When
|
|
31
|
+
* omitted, the engine resolves it from `circle gateway balance`.
|
|
32
|
+
*/
|
|
33
|
+
buyerWalletAddress?: `0x${string}`;
|
|
34
|
+
/** Alias for `buyerWalletAddress`. */
|
|
35
|
+
backingEOA?: `0x${string}`;
|
|
20
36
|
/** Circle CLI chain name. Rubicon real reads settle on Arc Testnet by default. */
|
|
21
37
|
chain?: string;
|
|
22
38
|
/** Circle CLI binary name or path. */
|
|
@@ -33,10 +49,10 @@ interface TypedDataRequest {
|
|
|
33
49
|
}
|
|
34
50
|
|
|
35
51
|
/**
|
|
36
|
-
* Circle CLI / Agent Wallet payment engine. It
|
|
37
|
-
*
|
|
38
|
-
*
|
|
39
|
-
*
|
|
52
|
+
* Circle CLI / Agent Wallet payment engine. It delegates EIP-712 signing to
|
|
53
|
+
* `circle wallet sign typed-data`, so agents never need raw private keys or
|
|
54
|
+
* hand-built Circle / Arc payloads. Current gateways may call the legacy
|
|
55
|
+
* one-word method as a chunk-compatibility path.
|
|
40
56
|
*/
|
|
41
57
|
export class CircleCliGatewayPaymentEngine implements AgentPaymentEngine {
|
|
42
58
|
private readonly x402 = new x402Client();
|
|
@@ -44,7 +60,8 @@ export class CircleCliGatewayPaymentEngine implements AgentPaymentEngine {
|
|
|
44
60
|
|
|
45
61
|
constructor(options: CircleCliGatewayPaymentEngineOptions = {}) {
|
|
46
62
|
this.signer = new CircleCliGatewaySigner({
|
|
47
|
-
|
|
63
|
+
agentWalletAddress: options.agentWalletAddress ?? options.walletAddress,
|
|
64
|
+
buyerWalletAddress: options.buyerWalletAddress ?? options.backingEOA,
|
|
48
65
|
chain: options.chain ?? "ARC-TESTNET",
|
|
49
66
|
command: options.command ?? "circle",
|
|
50
67
|
runner: options.runner ?? runCircleCli,
|
|
@@ -57,30 +74,56 @@ export class CircleCliGatewayPaymentEngine implements AgentPaymentEngine {
|
|
|
57
74
|
|
|
58
75
|
async createWordPayment(session: StartSessionResponse): Promise<StreamPaymentRequest> {
|
|
59
76
|
if (!session.paymentRequired) {
|
|
60
|
-
throw new Error("Session did not include
|
|
77
|
+
throw new Error("Session did not include a legacy x402 payment requirement");
|
|
61
78
|
}
|
|
62
79
|
await this.signer.ensureAddress();
|
|
63
80
|
return {
|
|
64
81
|
paymentPayload: await this.x402.createPaymentPayload(session.paymentRequired as never),
|
|
65
82
|
};
|
|
66
83
|
}
|
|
84
|
+
|
|
85
|
+
async createChunkPayment(session: StartSessionResponse, input: { nextSequence: number; maxWords: number }): Promise<StreamPaymentRequest> {
|
|
86
|
+
if (!session.paymentRequired) {
|
|
87
|
+
throw new Error("Session did not include an x402 payment requirement");
|
|
88
|
+
}
|
|
89
|
+
await this.signer.ensureAddress();
|
|
90
|
+
const amountAtomic = BigInt(session.wordPaymentAtomic) * BigInt(input.maxWords);
|
|
91
|
+
return {
|
|
92
|
+
paymentPayload: await this.x402.createPaymentPayload(
|
|
93
|
+
withChunkRequirement(session.paymentRequired, {
|
|
94
|
+
amountAtomic: `${amountAtomic}`,
|
|
95
|
+
maxWords: input.maxWords,
|
|
96
|
+
nextSequence: input.nextSequence,
|
|
97
|
+
sessionId: session.sessionId,
|
|
98
|
+
}) as never,
|
|
99
|
+
),
|
|
100
|
+
maxWords: input.maxWords,
|
|
101
|
+
};
|
|
102
|
+
}
|
|
67
103
|
}
|
|
68
104
|
|
|
69
|
-
class CircleCliGatewaySigner {
|
|
105
|
+
export class CircleCliGatewaySigner {
|
|
106
|
+
agentWalletAddress: `0x${string}` = "0x0000000000000000000000000000000000000000";
|
|
70
107
|
address: `0x${string}` = "0x0000000000000000000000000000000000000000";
|
|
71
108
|
private resolved = false;
|
|
72
109
|
private resolving?: Promise<void>;
|
|
73
110
|
|
|
74
111
|
constructor(
|
|
75
112
|
private readonly options: {
|
|
76
|
-
|
|
113
|
+
agentWalletAddress?: `0x${string}`;
|
|
114
|
+
buyerWalletAddress?: `0x${string}`;
|
|
77
115
|
chain: string;
|
|
78
116
|
command: string;
|
|
79
117
|
runner: CircleCliRunner;
|
|
80
118
|
},
|
|
81
119
|
) {
|
|
82
|
-
if (options.
|
|
83
|
-
this.
|
|
120
|
+
if (options.agentWalletAddress) {
|
|
121
|
+
this.agentWalletAddress = options.agentWalletAddress;
|
|
122
|
+
}
|
|
123
|
+
if (options.buyerWalletAddress) {
|
|
124
|
+
this.address = options.buyerWalletAddress;
|
|
125
|
+
}
|
|
126
|
+
if (options.agentWalletAddress && options.buyerWalletAddress) {
|
|
84
127
|
this.resolved = true;
|
|
85
128
|
}
|
|
86
129
|
}
|
|
@@ -101,7 +144,7 @@ class CircleCliGatewaySigner {
|
|
|
101
144
|
"typed-data",
|
|
102
145
|
serializeTypedData(toEip712Payload(typed)),
|
|
103
146
|
"--address",
|
|
104
|
-
this.
|
|
147
|
+
this.agentWalletAddress,
|
|
105
148
|
"--chain",
|
|
106
149
|
this.options.chain,
|
|
107
150
|
"--quiet",
|
|
@@ -110,6 +153,15 @@ class CircleCliGatewaySigner {
|
|
|
110
153
|
}
|
|
111
154
|
|
|
112
155
|
private async resolveAddress(): Promise<void> {
|
|
156
|
+
const agentWalletAddress =
|
|
157
|
+
this.options.agentWalletAddress ?? (await this.resolveAgentWalletAddress());
|
|
158
|
+
this.agentWalletAddress = agentWalletAddress;
|
|
159
|
+
this.address =
|
|
160
|
+
this.options.buyerWalletAddress ?? (await this.resolveBackingEOA(agentWalletAddress));
|
|
161
|
+
this.resolved = true;
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
private async resolveAgentWalletAddress(): Promise<`0x${string}`> {
|
|
113
165
|
const output = await this.options.runner(this.options.command, [
|
|
114
166
|
"wallet",
|
|
115
167
|
"list",
|
|
@@ -120,9 +172,21 @@ class CircleCliGatewaySigner {
|
|
|
120
172
|
"--output",
|
|
121
173
|
"json",
|
|
122
174
|
]);
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
175
|
+
return parseCircleCliWalletAddress(output);
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
private async resolveBackingEOA(agentWalletAddress: `0x${string}`): Promise<`0x${string}`> {
|
|
179
|
+
const output = await this.options.runner(this.options.command, [
|
|
180
|
+
"gateway",
|
|
181
|
+
"balance",
|
|
182
|
+
"--address",
|
|
183
|
+
agentWalletAddress,
|
|
184
|
+
"--chain",
|
|
185
|
+
this.options.chain,
|
|
186
|
+
"--output",
|
|
187
|
+
"json",
|
|
188
|
+
]);
|
|
189
|
+
return parseCircleGatewayBackingEOA(output);
|
|
126
190
|
}
|
|
127
191
|
}
|
|
128
192
|
|
|
@@ -173,7 +237,16 @@ export function parseCircleCliWalletAddress(output: string): `0x${string}` {
|
|
|
173
237
|
if (unique.length === 0) {
|
|
174
238
|
throw new Error("Circle CLI did not return an Agent Wallet address");
|
|
175
239
|
}
|
|
176
|
-
throw new Error("Multiple Circle Agent Wallets found; pass
|
|
240
|
+
throw new Error("Multiple Circle Agent Wallets found; pass agentWalletAddress explicitly");
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
export function parseCircleGatewayBackingEOA(output: string): `0x${string}` {
|
|
244
|
+
const parsed = parseJson(output);
|
|
245
|
+
const backingEOA = findString(parsed, ["data.backingEOA", "backingEOA"]);
|
|
246
|
+
if (backingEOA && isAddress(backingEOA)) {
|
|
247
|
+
return backingEOA;
|
|
248
|
+
}
|
|
249
|
+
throw new Error("Circle Gateway balance did not return a backingEOA address");
|
|
177
250
|
}
|
|
178
251
|
|
|
179
252
|
function collectWalletCandidates(value: unknown): Record<string, unknown>[] {
|
|
@@ -209,6 +282,32 @@ function findString(value: unknown, keys: string[]): string | undefined {
|
|
|
209
282
|
return undefined;
|
|
210
283
|
}
|
|
211
284
|
|
|
285
|
+
function withChunkRequirement(
|
|
286
|
+
paymentRequired: unknown,
|
|
287
|
+
chunk: { amountAtomic: `${bigint}`; maxWords: number; nextSequence: number; sessionId: string },
|
|
288
|
+
): unknown {
|
|
289
|
+
const source = paymentRequired as { accepts?: Array<Record<string, unknown>> };
|
|
290
|
+
if (!Array.isArray(source.accepts)) {
|
|
291
|
+
return paymentRequired;
|
|
292
|
+
}
|
|
293
|
+
return {
|
|
294
|
+
...(paymentRequired as Record<string, unknown>),
|
|
295
|
+
accepts: source.accepts.map((accept) => ({
|
|
296
|
+
...accept,
|
|
297
|
+
amount: chunk.amountAtomic,
|
|
298
|
+
extra: {
|
|
299
|
+
...((accept.extra as Record<string, unknown> | undefined) ?? {}),
|
|
300
|
+
amountAtomic: chunk.amountAtomic,
|
|
301
|
+
maxWords: chunk.maxWords,
|
|
302
|
+
sequence: chunk.nextSequence,
|
|
303
|
+
authorizationMode: "chunk",
|
|
304
|
+
nonce: `${chunk.sessionId}:${chunk.nextSequence}:${chunk.maxWords}`,
|
|
305
|
+
idempotencyKey: `${chunk.sessionId}:${chunk.nextSequence}:${chunk.maxWords}`,
|
|
306
|
+
},
|
|
307
|
+
})),
|
|
308
|
+
};
|
|
309
|
+
}
|
|
310
|
+
|
|
212
311
|
function parseJson(value: string): unknown {
|
|
213
312
|
try {
|
|
214
313
|
return JSON.parse(value);
|
package/src/payment-engine.ts
CHANGED
|
@@ -1,20 +1,54 @@
|
|
|
1
|
-
import type { StartSessionResponse, StreamPaymentRequest } from "@rubicon-caliga/core";
|
|
1
|
+
import type { StartSessionResponse, StreamAuthorizationRequest, StreamPaymentRequest } from "@rubicon-caliga/core";
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
|
-
* Produces
|
|
5
|
-
*
|
|
4
|
+
* Produces Circle / Arc authorization payloads for Rubicon reads. Preferred
|
|
5
|
+
* engines authorize a whole session once; fallback engines authorize chunks.
|
|
6
|
+
* Legacy one-word payloads remain supported for older gateways and tests.
|
|
6
7
|
*/
|
|
7
8
|
export interface AgentPaymentEngine {
|
|
9
|
+
createSessionAuthorization?(session: StartSessionResponse): Promise<StreamAuthorizationRequest>;
|
|
10
|
+
createChunkPayment?(session: StartSessionResponse, input: { nextSequence: number; maxWords: number }): Promise<StreamPaymentRequest>;
|
|
11
|
+
/** @deprecated Compatibility path for one-word x402 gateways. */
|
|
8
12
|
createWordPayment(session: StartSessionResponse): Promise<StreamPaymentRequest>;
|
|
9
13
|
}
|
|
10
14
|
|
|
11
15
|
/**
|
|
12
|
-
* Development engine. Declares the
|
|
13
|
-
* for use against a dev-mode gateway. NOT for production.
|
|
16
|
+
* Development engine. Declares the authorized amount without settling real
|
|
17
|
+
* funds, for use against a dev-mode gateway. NOT for production.
|
|
14
18
|
*/
|
|
15
19
|
export class StaticPaymentEngine implements AgentPaymentEngine {
|
|
16
20
|
constructor(private readonly network = "eip155:5042002") {}
|
|
17
21
|
|
|
22
|
+
async createSessionAuthorization(session: StartSessionResponse): Promise<StreamAuthorizationRequest> {
|
|
23
|
+
return {
|
|
24
|
+
authorizationPayload: {
|
|
25
|
+
scheme: "development-static",
|
|
26
|
+
network: this.network,
|
|
27
|
+
sessionId: session.sessionId,
|
|
28
|
+
amountAtomic: session.authorizationRequired?.maxAuthorizedAtomic ?? session.maxArticlePriceAtomic,
|
|
29
|
+
meteringUnit: "word",
|
|
30
|
+
authorizationMode: "session",
|
|
31
|
+
},
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
async createChunkPayment(session: StartSessionResponse, input: { nextSequence: number; maxWords: number }): Promise<StreamPaymentRequest> {
|
|
36
|
+
const amountAtomic = BigInt(session.wordPaymentAtomic) * BigInt(input.maxWords);
|
|
37
|
+
return {
|
|
38
|
+
paymentPayload: {
|
|
39
|
+
scheme: "development-static",
|
|
40
|
+
network: this.network,
|
|
41
|
+
sessionId: session.sessionId,
|
|
42
|
+
amountAtomic: `${amountAtomic}`,
|
|
43
|
+
meteringUnit: "word",
|
|
44
|
+
authorizationMode: "chunk",
|
|
45
|
+
maxWords: input.maxWords,
|
|
46
|
+
nextSequence: input.nextSequence,
|
|
47
|
+
},
|
|
48
|
+
maxWords: input.maxWords,
|
|
49
|
+
};
|
|
50
|
+
}
|
|
51
|
+
|
|
18
52
|
async createWordPayment(session: StartSessionResponse): Promise<StreamPaymentRequest> {
|
|
19
53
|
return {
|
|
20
54
|
paymentPayload: {
|