@rubicon-caliga/agent-sdk 0.1.2 → 0.1.4
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 +28 -24
- package/dist/agent-client.d.ts +41 -5
- package/dist/agent-client.d.ts.map +1 -1
- package/dist/agent-client.js +120 -4
- package/dist/agent-client.js.map +1 -1
- package/dist/agent-client.test.js +229 -3
- package/dist/agent-client.test.js.map +1 -1
- package/dist/circle-agent-wallet.d.ts +10 -6
- package/dist/circle-agent-wallet.d.ts.map +1 -1
- package/dist/circle-agent-wallet.js +46 -8
- package/dist/circle-agent-wallet.js.map +1 -1
- package/dist/circle-cli-gateway-payment.d.ts +8 -4
- package/dist/circle-cli-gateway-payment.d.ts.map +1 -1
- package/dist/circle-cli-gateway-payment.js +43 -5
- package/dist/circle-cli-gateway-payment.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 +241 -3
- package/src/agent-client.ts +148 -4
- package/src/circle-agent-wallet.ts +55 -12
- package/src/circle-cli-gateway-payment.ts +50 -5
- package/src/payment-engine.ts +39 -5
|
@@ -2,10 +2,8 @@ import type { StartSessionResponse, StreamPaymentRequest } from "@rubicon-caliga
|
|
|
2
2
|
import { x402Client } from "@x402/core/client";
|
|
3
3
|
import { registerBatchScheme } from "@circle-fin/x402-batching/client";
|
|
4
4
|
import { ExactEvmScheme } from "@x402/evm/exact/client";
|
|
5
|
-
import
|
|
6
|
-
|
|
7
|
-
type CircleDeveloperControlledWalletsClient,
|
|
8
|
-
} from "@circle-fin/developer-controlled-wallets";
|
|
5
|
+
import * as CircleWallets from "@circle-fin/developer-controlled-wallets";
|
|
6
|
+
import type { CircleDeveloperControlledWalletsClient } from "@circle-fin/developer-controlled-wallets";
|
|
9
7
|
import type { AgentPaymentEngine } from "./payment-engine.js";
|
|
10
8
|
|
|
11
9
|
export interface CircleAgentWalletEngineOptions {
|
|
@@ -13,7 +11,7 @@ export interface CircleAgentWalletEngineOptions {
|
|
|
13
11
|
apiKey: string;
|
|
14
12
|
/** Entity secret registered for the Circle developer account. */
|
|
15
13
|
entitySecret: string;
|
|
16
|
-
/** The Agent Wallet that holds USDC and signs
|
|
14
|
+
/** The Agent Wallet that holds USDC and signs Circle / Arc authorizations. */
|
|
17
15
|
walletId: string;
|
|
18
16
|
/**
|
|
19
17
|
* The wallet's on-chain address. Optional — when omitted it is resolved once
|
|
@@ -80,7 +78,7 @@ class CircleAgentWalletSigner {
|
|
|
80
78
|
const res = await this.client.signTypedData({
|
|
81
79
|
walletId: this.walletId,
|
|
82
80
|
data: serializeTypedData(toEip712Payload(typed)),
|
|
83
|
-
memo: "Rubicon
|
|
81
|
+
memo: "Rubicon reading authorization",
|
|
84
82
|
});
|
|
85
83
|
const signature = res.data?.signature;
|
|
86
84
|
if (!signature) {
|
|
@@ -91,10 +89,10 @@ class CircleAgentWalletSigner {
|
|
|
91
89
|
}
|
|
92
90
|
|
|
93
91
|
/**
|
|
94
|
-
* Circle Agent Wallet engine. Signs
|
|
95
|
-
* custodial Circle Agent Wallet
|
|
96
|
-
*
|
|
97
|
-
*
|
|
92
|
+
* Circle Agent Wallet engine. Signs gateway authorization terms with a
|
|
93
|
+
* custodial Circle Agent Wallet so the buyer agent never handles a local
|
|
94
|
+
* signing key. Current gateways may still call the legacy one-word method as a
|
|
95
|
+
* chunk-compatibility path.
|
|
98
96
|
*/
|
|
99
97
|
export class CircleAgentWalletEngine implements AgentPaymentEngine {
|
|
100
98
|
private readonly x402 = new x402Client();
|
|
@@ -103,7 +101,7 @@ export class CircleAgentWalletEngine implements AgentPaymentEngine {
|
|
|
103
101
|
constructor(options: CircleAgentWalletEngineOptions) {
|
|
104
102
|
const client =
|
|
105
103
|
options.client ??
|
|
106
|
-
initiateDeveloperControlledWalletsClient({
|
|
104
|
+
CircleWallets.initiateDeveloperControlledWalletsClient({
|
|
107
105
|
apiKey: options.apiKey,
|
|
108
106
|
entitySecret: options.entitySecret,
|
|
109
107
|
...(options.baseUrl ? { baseUrl: options.baseUrl } : {}),
|
|
@@ -119,7 +117,7 @@ export class CircleAgentWalletEngine implements AgentPaymentEngine {
|
|
|
119
117
|
|
|
120
118
|
async createWordPayment(session: StartSessionResponse): Promise<StreamPaymentRequest> {
|
|
121
119
|
if (!session.paymentRequired) {
|
|
122
|
-
throw new Error("Session did not include
|
|
120
|
+
throw new Error("Session did not include a legacy x402 payment requirement");
|
|
123
121
|
}
|
|
124
122
|
// Resolve the wallet address up front so the synchronous `address` read
|
|
125
123
|
// inside createPaymentPayload sees a real value.
|
|
@@ -128,6 +126,25 @@ export class CircleAgentWalletEngine implements AgentPaymentEngine {
|
|
|
128
126
|
paymentPayload: await this.x402.createPaymentPayload(session.paymentRequired as never),
|
|
129
127
|
};
|
|
130
128
|
}
|
|
129
|
+
|
|
130
|
+
async createChunkPayment(session: StartSessionResponse, input: { nextSequence: number; maxWords: number }): Promise<StreamPaymentRequest> {
|
|
131
|
+
if (!session.paymentRequired) {
|
|
132
|
+
throw new Error("Session did not include an x402 payment requirement");
|
|
133
|
+
}
|
|
134
|
+
await this.signer.ensureAddress();
|
|
135
|
+
const amountAtomic = BigInt(session.wordPaymentAtomic) * BigInt(input.maxWords);
|
|
136
|
+
return {
|
|
137
|
+
paymentPayload: await this.x402.createPaymentPayload(
|
|
138
|
+
withChunkRequirement(session.paymentRequired, {
|
|
139
|
+
amountAtomic: `${amountAtomic}`,
|
|
140
|
+
maxWords: input.maxWords,
|
|
141
|
+
nextSequence: input.nextSequence,
|
|
142
|
+
sessionId: session.sessionId,
|
|
143
|
+
}) as never,
|
|
144
|
+
),
|
|
145
|
+
maxWords: input.maxWords,
|
|
146
|
+
};
|
|
147
|
+
}
|
|
131
148
|
}
|
|
132
149
|
|
|
133
150
|
/**
|
|
@@ -208,3 +225,29 @@ function isEip712Field(field: unknown): field is { name: string; type: string }
|
|
|
208
225
|
typeof (field as { type?: unknown }).type === "string"
|
|
209
226
|
);
|
|
210
227
|
}
|
|
228
|
+
|
|
229
|
+
function withChunkRequirement(
|
|
230
|
+
paymentRequired: unknown,
|
|
231
|
+
chunk: { amountAtomic: `${bigint}`; maxWords: number; nextSequence: number; sessionId: string },
|
|
232
|
+
): unknown {
|
|
233
|
+
const source = paymentRequired as { accepts?: Array<Record<string, unknown>> };
|
|
234
|
+
if (!Array.isArray(source.accepts)) {
|
|
235
|
+
return paymentRequired;
|
|
236
|
+
}
|
|
237
|
+
return {
|
|
238
|
+
...(paymentRequired as Record<string, unknown>),
|
|
239
|
+
accepts: source.accepts.map((accept) => ({
|
|
240
|
+
...accept,
|
|
241
|
+
amount: chunk.amountAtomic,
|
|
242
|
+
extra: {
|
|
243
|
+
...((accept.extra as Record<string, unknown> | undefined) ?? {}),
|
|
244
|
+
amountAtomic: chunk.amountAtomic,
|
|
245
|
+
maxWords: chunk.maxWords,
|
|
246
|
+
sequence: chunk.nextSequence,
|
|
247
|
+
authorizationMode: "chunk",
|
|
248
|
+
nonce: `${chunk.sessionId}:${chunk.nextSequence}:${chunk.maxWords}`,
|
|
249
|
+
idempotencyKey: `${chunk.sessionId}:${chunk.nextSequence}:${chunk.maxWords}`,
|
|
250
|
+
},
|
|
251
|
+
})),
|
|
252
|
+
};
|
|
253
|
+
}
|
|
@@ -49,10 +49,10 @@ interface TypedDataRequest {
|
|
|
49
49
|
}
|
|
50
50
|
|
|
51
51
|
/**
|
|
52
|
-
* Circle CLI / Agent Wallet payment engine. It
|
|
53
|
-
*
|
|
54
|
-
*
|
|
55
|
-
*
|
|
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.
|
|
56
56
|
*/
|
|
57
57
|
export class CircleCliGatewayPaymentEngine implements AgentPaymentEngine {
|
|
58
58
|
private readonly x402 = new x402Client();
|
|
@@ -74,13 +74,32 @@ export class CircleCliGatewayPaymentEngine implements AgentPaymentEngine {
|
|
|
74
74
|
|
|
75
75
|
async createWordPayment(session: StartSessionResponse): Promise<StreamPaymentRequest> {
|
|
76
76
|
if (!session.paymentRequired) {
|
|
77
|
-
throw new Error("Session did not include
|
|
77
|
+
throw new Error("Session did not include a legacy x402 payment requirement");
|
|
78
78
|
}
|
|
79
79
|
await this.signer.ensureAddress();
|
|
80
80
|
return {
|
|
81
81
|
paymentPayload: await this.x402.createPaymentPayload(session.paymentRequired as never),
|
|
82
82
|
};
|
|
83
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
|
+
}
|
|
84
103
|
}
|
|
85
104
|
|
|
86
105
|
export class CircleCliGatewaySigner {
|
|
@@ -263,6 +282,32 @@ function findString(value: unknown, keys: string[]): string | undefined {
|
|
|
263
282
|
return undefined;
|
|
264
283
|
}
|
|
265
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
|
+
|
|
266
311
|
function parseJson(value: string): unknown {
|
|
267
312
|
try {
|
|
268
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: {
|