@teneo-protocol/sdk 2.0.0 → 2.2.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/.eslintrc.json +11 -2
- package/.github/CODEOWNERS +2 -0
- package/.github/ISSUE_TEMPLATE/01-bug.yml +85 -0
- package/.github/ISSUE_TEMPLATE/config.yml +8 -0
- package/.github/ISSUE_TEMPLATE/feature_request.md +22 -0
- package/.github/workflows/claude-code-review.yml +8 -3
- package/.github/workflows/claude-reviewer.yml +6 -4
- package/.github/workflows/publish-npm.yml +1 -0
- package/.github/workflows/push-to-main.yml +1 -1
- package/.github/workflows/top-issue.yml +102 -0
- package/CHANGELOG.md +69 -0
- package/CONCEPTS.md +747 -0
- package/README.md +178 -8
- package/dist/constants.js +8 -8
- package/dist/constants.js.map +1 -1
- package/dist/core/websocket-client.d.ts +15 -3
- package/dist/core/websocket-client.d.ts.map +1 -1
- package/dist/core/websocket-client.js +52 -13
- package/dist/core/websocket-client.js.map +1 -1
- package/dist/formatters/response-formatter.js +4 -0
- package/dist/formatters/response-formatter.js.map +1 -1
- package/dist/handlers/message-handler-registry.js +2 -1
- package/dist/handlers/message-handler-registry.js.map +1 -1
- package/dist/handlers/message-handlers/agent-details-response-handler.d.ts +1666 -0
- package/dist/handlers/message-handlers/agent-details-response-handler.d.ts.map +1 -0
- package/dist/handlers/message-handlers/agent-details-response-handler.js +38 -0
- package/dist/handlers/message-handlers/agent-details-response-handler.js.map +1 -0
- package/dist/handlers/message-handlers/agent-room-operation-response-handler.d.ts.map +1 -1
- package/dist/handlers/message-handlers/agent-room-operation-response-handler.js +2 -5
- package/dist/handlers/message-handlers/agent-room-operation-response-handler.js.map +1 -1
- package/dist/handlers/message-handlers/agent-selected-handler.js +2 -5
- package/dist/handlers/message-handlers/agent-selected-handler.js.map +1 -1
- package/dist/handlers/message-handlers/agent-status-update-handler.d.ts +783 -0
- package/dist/handlers/message-handlers/agent-status-update-handler.d.ts.map +1 -1
- package/dist/handlers/message-handlers/agent-status-update-handler.js +2 -5
- package/dist/handlers/message-handlers/agent-status-update-handler.js.map +1 -1
- package/dist/handlers/message-handlers/agents-list-handler.js +2 -5
- package/dist/handlers/message-handlers/agents-list-handler.js.map +1 -1
- package/dist/handlers/message-handlers/all-agents-response-handler.d.ts +439 -0
- package/dist/handlers/message-handlers/all-agents-response-handler.d.ts.map +1 -0
- package/dist/handlers/message-handlers/all-agents-response-handler.js +36 -0
- package/dist/handlers/message-handlers/all-agents-response-handler.js.map +1 -0
- package/dist/handlers/message-handlers/auth-error-handler.js +2 -5
- package/dist/handlers/message-handlers/auth-error-handler.js.map +1 -1
- package/dist/handlers/message-handlers/auth-message-handler.d.ts.map +1 -1
- package/dist/handlers/message-handlers/auth-message-handler.js +6 -5
- package/dist/handlers/message-handlers/auth-message-handler.js.map +1 -1
- package/dist/handlers/message-handlers/auth-required-handler.js +2 -5
- package/dist/handlers/message-handlers/auth-required-handler.js.map +1 -1
- package/dist/handlers/message-handlers/auth-success-handler.d.ts.map +1 -1
- package/dist/handlers/message-handlers/auth-success-handler.js +6 -5
- package/dist/handlers/message-handlers/auth-success-handler.js.map +1 -1
- package/dist/handlers/message-handlers/base-handler.d.ts +4 -4
- package/dist/handlers/message-handlers/base-handler.d.ts.map +1 -1
- package/dist/handlers/message-handlers/base-handler.js +3 -1
- package/dist/handlers/message-handlers/base-handler.js.map +1 -1
- package/dist/handlers/message-handlers/challenge-handler.js +3 -2
- package/dist/handlers/message-handlers/challenge-handler.js.map +1 -1
- package/dist/handlers/message-handlers/error-message-handler.js +2 -5
- package/dist/handlers/message-handlers/error-message-handler.js.map +1 -1
- package/dist/handlers/message-handlers/index.d.ts +6 -0
- package/dist/handlers/message-handlers/index.d.ts.map +1 -1
- package/dist/handlers/message-handlers/index.js +33 -1
- package/dist/handlers/message-handlers/index.js.map +1 -1
- package/dist/handlers/message-handlers/list-available-agents-handler.d.ts +783 -0
- package/dist/handlers/message-handlers/list-available-agents-handler.d.ts.map +1 -1
- package/dist/handlers/message-handlers/list-available-agents-handler.js +2 -5
- package/dist/handlers/message-handlers/list-available-agents-handler.js.map +1 -1
- package/dist/handlers/message-handlers/list-room-agents-handler.d.ts +783 -0
- package/dist/handlers/message-handlers/list-room-agents-handler.d.ts.map +1 -1
- package/dist/handlers/message-handlers/list-room-agents-handler.js +2 -5
- package/dist/handlers/message-handlers/list-room-agents-handler.js.map +1 -1
- package/dist/handlers/message-handlers/list-rooms-response-handler.d.ts +2 -199
- package/dist/handlers/message-handlers/list-rooms-response-handler.d.ts.map +1 -1
- package/dist/handlers/message-handlers/list-rooms-response-handler.js +4 -6
- package/dist/handlers/message-handlers/list-rooms-response-handler.js.map +1 -1
- package/dist/handlers/message-handlers/ping-pong-handler.js +4 -10
- package/dist/handlers/message-handlers/ping-pong-handler.js.map +1 -1
- package/dist/handlers/message-handlers/rate-limit-notification-handler.d.ts +94 -0
- package/dist/handlers/message-handlers/rate-limit-notification-handler.d.ts.map +1 -0
- package/dist/handlers/message-handlers/rate-limit-notification-handler.js +35 -0
- package/dist/handlers/message-handlers/rate-limit-notification-handler.js.map +1 -0
- package/dist/handlers/message-handlers/regular-message-handler.d.ts.map +1 -1
- package/dist/handlers/message-handlers/regular-message-handler.js +4 -6
- package/dist/handlers/message-handlers/regular-message-handler.js.map +1 -1
- package/dist/handlers/message-handlers/room-operation-response-handler.d.ts.map +1 -1
- package/dist/handlers/message-handlers/room-operation-response-handler.js +2 -5
- package/dist/handlers/message-handlers/room-operation-response-handler.js.map +1 -1
- package/dist/handlers/message-handlers/subscribe-response-handler.d.ts.map +1 -1
- package/dist/handlers/message-handlers/subscribe-response-handler.js +4 -6
- package/dist/handlers/message-handlers/subscribe-response-handler.js.map +1 -1
- package/dist/handlers/message-handlers/task-quote-handler.d.ts +14 -0
- package/dist/handlers/message-handlers/task-quote-handler.d.ts.map +1 -0
- package/dist/handlers/message-handlers/task-quote-handler.js +29 -0
- package/dist/handlers/message-handlers/task-quote-handler.js.map +1 -0
- package/dist/handlers/message-handlers/task-response-handler.js +2 -5
- package/dist/handlers/message-handlers/task-response-handler.js.map +1 -1
- package/dist/handlers/message-handlers/types.d.ts +21 -9
- package/dist/handlers/message-handlers/types.d.ts.map +1 -1
- package/dist/handlers/message-handlers/unsubscribe-response-handler.d.ts.map +1 -1
- package/dist/handlers/message-handlers/unsubscribe-response-handler.js +4 -6
- package/dist/handlers/message-handlers/unsubscribe-response-handler.js.map +1 -1
- package/dist/handlers/message-handlers/user-authenticated-handler.d.ts +40 -0
- package/dist/handlers/message-handlers/user-authenticated-handler.d.ts.map +1 -0
- package/dist/handlers/message-handlers/user-authenticated-handler.js +28 -0
- package/dist/handlers/message-handlers/user-authenticated-handler.js.map +1 -0
- package/dist/handlers/message-handlers/user-count-handler.d.ts +49 -0
- package/dist/handlers/message-handlers/user-count-handler.d.ts.map +1 -0
- package/dist/handlers/message-handlers/user-count-handler.js +31 -0
- package/dist/handlers/message-handlers/user-count-handler.js.map +1 -0
- package/dist/handlers/webhook-handler.d.ts +1 -1
- package/dist/handlers/webhook-handler.d.ts.map +1 -1
- package/dist/handlers/webhook-handler.js +14 -5
- package/dist/handlers/webhook-handler.js.map +1 -1
- package/dist/index.d.ts +6 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +19 -2
- package/dist/index.js.map +1 -1
- package/dist/managers/admin-manager.d.ts +116 -0
- package/dist/managers/admin-manager.d.ts.map +1 -0
- package/dist/managers/admin-manager.js +169 -0
- package/dist/managers/admin-manager.js.map +1 -0
- package/dist/managers/agent-registry.d.ts +52 -1
- package/dist/managers/agent-registry.d.ts.map +1 -1
- package/dist/managers/agent-registry.js +145 -6
- package/dist/managers/agent-registry.js.map +1 -1
- package/dist/managers/agent-room-manager.d.ts +1 -7
- package/dist/managers/agent-room-manager.d.ts.map +1 -1
- package/dist/managers/agent-room-manager.js +83 -36
- package/dist/managers/agent-room-manager.js.map +1 -1
- package/dist/managers/connection-manager.js +2 -0
- package/dist/managers/connection-manager.js.map +1 -1
- package/dist/managers/index.d.ts +2 -1
- package/dist/managers/index.d.ts.map +1 -1
- package/dist/managers/index.js +3 -1
- package/dist/managers/index.js.map +1 -1
- package/dist/managers/message-router.d.ts +56 -5
- package/dist/managers/message-router.d.ts.map +1 -1
- package/dist/managers/message-router.js +155 -8
- package/dist/managers/message-router.js.map +1 -1
- package/dist/managers/room-management-manager.d.ts.map +1 -1
- package/dist/managers/room-management-manager.js +9 -7
- package/dist/managers/room-management-manager.js.map +1 -1
- package/dist/managers/room-manager.d.ts +7 -5
- package/dist/managers/room-manager.d.ts.map +1 -1
- package/dist/managers/room-manager.js +14 -10
- package/dist/managers/room-manager.js.map +1 -1
- package/dist/payments/index.d.ts +5 -0
- package/dist/payments/index.d.ts.map +1 -0
- package/dist/payments/index.js +21 -0
- package/dist/payments/index.js.map +1 -0
- package/dist/payments/payment-client.d.ts +74 -0
- package/dist/payments/payment-client.d.ts.map +1 -0
- package/dist/payments/payment-client.js +207 -0
- package/dist/payments/payment-client.js.map +1 -0
- package/dist/teneo-sdk.d.ts +135 -21
- package/dist/teneo-sdk.d.ts.map +1 -1
- package/dist/teneo-sdk.js +268 -26
- package/dist/teneo-sdk.js.map +1 -1
- package/dist/types/categories.d.ts +22 -0
- package/dist/types/categories.d.ts.map +1 -0
- package/dist/types/categories.js +40 -0
- package/dist/types/categories.js.map +1 -0
- package/dist/types/config.d.ts +79 -8
- package/dist/types/config.d.ts.map +1 -1
- package/dist/types/config.js +62 -5
- package/dist/types/config.js.map +1 -1
- package/dist/types/error-codes.d.ts +8 -0
- package/dist/types/error-codes.d.ts.map +1 -1
- package/dist/types/error-codes.js +9 -0
- package/dist/types/error-codes.js.map +1 -1
- package/dist/types/events.d.ts +44 -0
- package/dist/types/events.d.ts.map +1 -1
- package/dist/types/events.js +19 -1
- package/dist/types/events.js.map +1 -1
- package/dist/types/index.d.ts +3 -2
- package/dist/types/index.d.ts.map +1 -1
- package/dist/types/index.js +33 -3
- package/dist/types/index.js.map +1 -1
- package/dist/types/messages.d.ts +11219 -423
- package/dist/types/messages.d.ts.map +1 -1
- package/dist/types/messages.js +249 -7
- package/dist/types/messages.js.map +1 -1
- package/dist/utils/bounded-queue.d.ts.map +1 -1
- package/dist/utils/bounded-queue.js +5 -2
- package/dist/utils/bounded-queue.js.map +1 -1
- package/dist/utils/circuit-breaker.js +11 -4
- package/dist/utils/circuit-breaker.js.map +1 -1
- package/dist/utils/deduplication-cache.js +3 -1
- package/dist/utils/deduplication-cache.js.map +1 -1
- package/dist/utils/event-waiter.d.ts +3 -3
- package/dist/utils/event-waiter.d.ts.map +1 -1
- package/dist/utils/index.d.ts +1 -0
- package/dist/utils/index.d.ts.map +1 -1
- package/dist/utils/index.js +4 -1
- package/dist/utils/index.js.map +1 -1
- package/dist/utils/logger.js.map +1 -1
- package/dist/utils/pricing-resolver.d.ts +26 -0
- package/dist/utils/pricing-resolver.d.ts.map +1 -0
- package/dist/utils/pricing-resolver.js +85 -0
- package/dist/utils/pricing-resolver.js.map +1 -0
- package/dist/utils/rate-limiter.d.ts.map +1 -1
- package/dist/utils/rate-limiter.js +6 -0
- package/dist/utils/rate-limiter.js.map +1 -1
- package/dist/utils/retry-policy.js +1 -0
- package/dist/utils/retry-policy.js.map +1 -1
- package/dist/utils/secure-private-key.js +3 -1
- package/dist/utils/secure-private-key.js.map +1 -1
- package/dist/utils/signature-verifier.d.ts.map +1 -1
- package/dist/utils/signature-verifier.js +3 -1
- package/dist/utils/signature-verifier.js.map +1 -1
- package/examples/.env.example +1 -1
- package/examples/agent-room-management-example.ts +10 -9
- package/examples/basic-usage.ts +3 -4
- package/examples/claude-agent-x-follower/.env.example +1 -1
- package/examples/claude-agent-x-follower/QUICKSTART.md +2 -2
- package/examples/claude-agent-x-follower/README.md +2 -2
- package/examples/claude-agent-x-follower/index.ts +120 -96
- package/examples/n8n-teneo/.env.example +1 -1
- package/examples/n8n-teneo/README.md +1 -1
- package/examples/n8n-teneo/index.ts +54 -44
- package/examples/nestjs-dashboard/.env.example +11 -0
- package/examples/nestjs-dashboard/README.md +297 -0
- package/examples/nestjs-dashboard/nest-cli.json +10 -0
- package/examples/nestjs-dashboard/package.json +44 -0
- package/examples/nestjs-dashboard/pnpm-lock.yaml +3079 -0
- package/examples/nestjs-dashboard/src/app.controller.ts +24 -0
- package/examples/nestjs-dashboard/src/app.module.ts +15 -0
- package/examples/nestjs-dashboard/src/main.ts +32 -0
- package/examples/nestjs-dashboard/src/public/dashboard.html +1144 -0
- package/examples/nestjs-dashboard/src/teneo/agents.controller.ts +54 -0
- package/examples/nestjs-dashboard/src/teneo/events.controller.ts +65 -0
- package/examples/nestjs-dashboard/src/teneo/messages.controller.ts +47 -0
- package/examples/nestjs-dashboard/src/teneo/rooms.controller.ts +258 -0
- package/examples/nestjs-dashboard/src/teneo/teneo.module.ts +13 -0
- package/examples/nestjs-dashboard/src/teneo/teneo.service.ts +484 -0
- package/examples/nestjs-dashboard/tsconfig.json +22 -0
- package/examples/openai-teneo/.env.example +1 -1
- package/examples/openai-teneo/README.md +2 -2
- package/examples/openai-teneo/index.ts +82 -71
- package/examples/production-dashboard/.env.example +1 -1
- package/examples/production-dashboard/README.md +1 -1
- package/examples/production-dashboard/server.ts +2 -2
- package/examples/room-management-example.ts +5 -8
- package/examples/usage/.env.example +1 -1
- package/examples/usage/01-connect.ts +3 -4
- package/examples/usage/02-list-agents.ts +2 -3
- package/examples/usage/03-pick-agent.ts +2 -3
- package/examples/usage/04-find-by-capability.ts +2 -3
- package/examples/usage/05-webhook-example.ts +2 -3
- package/examples/usage/06-simple-api-server.ts +2 -3
- package/examples/usage/07-event-listener.ts +2 -3
- package/examples/webhook-integration.ts +1 -1
- package/examples/x-influencer-battle-server.ts +2 -2
- package/package.json +4 -1
- package/src/core/websocket-client.test.ts +8 -3
- package/src/core/websocket-client.ts +36 -6
- package/src/formatters/response-formatter.test.ts +2 -0
- package/src/formatters/response-formatter.ts +3 -3
- package/src/handlers/message-handlers/agent-details-response-handler.ts +42 -0
- package/src/handlers/message-handlers/agent-room-operation-response-handler.ts +2 -8
- package/src/handlers/message-handlers/agent-status-update-handler.ts +3 -9
- package/src/handlers/message-handlers/all-agents-response-handler.ts +39 -0
- package/src/handlers/message-handlers/auth-message-handler.ts +5 -0
- package/src/handlers/message-handlers/auth-success-handler.ts +6 -1
- package/src/handlers/message-handlers/base-handler.ts +20 -7
- package/src/handlers/message-handlers/index.ts +34 -0
- package/src/handlers/message-handlers/list-room-agents-handler.ts +2 -5
- package/src/handlers/message-handlers/list-rooms-response-handler.ts +4 -2
- package/src/handlers/message-handlers/rate-limit-notification-handler.ts +45 -0
- package/src/handlers/message-handlers/regular-message-handler.ts +3 -2
- package/src/handlers/message-handlers/room-operation-response-handler.ts +3 -6
- package/src/handlers/message-handlers/subscribe-response-handler.ts +12 -2
- package/src/handlers/message-handlers/task-quote-handler.ts +31 -0
- package/src/handlers/message-handlers/types.ts +37 -9
- package/src/handlers/message-handlers/unsubscribe-response-handler.ts +12 -2
- package/src/handlers/message-handlers/user-authenticated-handler.ts +31 -0
- package/src/handlers/message-handlers/user-count-handler.ts +34 -0
- package/src/handlers/webhook-handler.test.ts +3 -2
- package/src/handlers/webhook-handler.ts +13 -7
- package/src/index.ts +21 -0
- package/src/managers/admin-manager.ts +249 -0
- package/src/managers/agent-registry.test.ts +2 -1
- package/src/managers/agent-registry.ts +170 -2
- package/src/managers/agent-room-manager.ts +98 -42
- package/src/managers/index.ts +13 -1
- package/src/managers/message-router.ts +215 -17
- package/src/managers/room-management-manager.ts +4 -7
- package/src/managers/room-manager.ts +11 -15
- package/src/payments/index.ts +22 -0
- package/src/payments/payment-client.ts +240 -0
- package/src/teneo-sdk.ts +302 -27
- package/src/types/categories.ts +45 -0
- package/src/types/config.ts +70 -2
- package/src/types/error-codes.ts +10 -0
- package/src/types/events.test.ts +1 -0
- package/src/types/events.ts +43 -0
- package/src/types/index.ts +56 -0
- package/src/types/messages.test.ts +2 -1
- package/src/types/messages.ts +307 -5
- package/src/utils/bounded-queue.test.ts +1 -1
- package/src/utils/bounded-queue.ts +2 -1
- package/src/utils/circuit-breaker.test.ts +1 -1
- package/src/utils/deduplication-cache.test.ts +1 -1
- package/src/utils/event-waiter.test.ts +1 -1
- package/src/utils/event-waiter.ts +3 -3
- package/src/utils/index.ts +7 -0
- package/src/utils/logger.ts +8 -8
- package/src/utils/pricing-resolver.ts +128 -0
- package/src/utils/rate-limiter.test.ts +1 -1
- package/src/utils/rate-limiter.ts +1 -0
- package/src/utils/signature-verifier.test.ts +2 -2
- package/src/utils/signature-verifier.ts +3 -2
- package/tests/.env.example +7 -0
- package/tests/direct-agent-test.ts +151 -0
- package/tests/integration/real-server.test.ts +2 -0
- package/tests/integration/room-management.test.ts +10 -8
- package/tests/integration/websocket.test.ts +4 -1
- package/tests/payment-flow-test.ts +147 -0
- package/tests/unit/handlers/agent-room-operation-response-handler.test.ts +17 -29
- package/tests/unit/handlers/agent-status-update-handler.test.ts +2 -6
- package/tests/unit/handlers/auth-success-handler-rooms.test.ts +1 -3
- package/tests/unit/handlers/list-available-agents-handler.test.ts +4 -12
- package/tests/unit/handlers/list-room-agents-handler.test.ts +2 -6
- package/tests/unit/handlers/room-operation-response-handler.test.ts +9 -36
- package/tests/unit/managers/agent-room-manager.test.ts +9 -16
- package/tests/unit/managers/room-management-manager.test.ts +21 -39
- package/tsconfig.json +2 -2
- package/vitest.config.ts +1 -0
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* MessageRouter - Manages message sending and routing
|
|
3
3
|
* Handles user messages, direct commands, and message-response patterns
|
|
4
|
+
* Supports quote-approve payment flow (v2.2.0)
|
|
4
5
|
*/
|
|
5
6
|
|
|
6
7
|
import { EventEmitter } from "eventemitter3";
|
|
@@ -11,11 +12,15 @@ import { ResponseFormatter, FormattedResponse } from "../formatters/response-for
|
|
|
11
12
|
import {
|
|
12
13
|
UserMessage,
|
|
13
14
|
createUserMessage,
|
|
15
|
+
createRequestTask,
|
|
16
|
+
createConfirmTask,
|
|
14
17
|
Logger,
|
|
15
18
|
ResponseFormat,
|
|
16
|
-
TaskResponseMessage
|
|
19
|
+
TaskResponseMessage,
|
|
20
|
+
TaskQuoteMessage,
|
|
21
|
+
PricingInfo
|
|
17
22
|
} from "../types";
|
|
18
|
-
import { SDKEvents, SDKError, ValidationError, AgentResponse } from "../types/events";
|
|
23
|
+
import { SDKEvents, SDKError, ValidationError, AgentResponse, PaymentError } from "../types/events";
|
|
19
24
|
import { ErrorCode } from "../types/error-codes";
|
|
20
25
|
import { TIMEOUTS } from "../constants";
|
|
21
26
|
import {
|
|
@@ -24,6 +29,8 @@ import {
|
|
|
24
29
|
AgentCommandContentSchema
|
|
25
30
|
} from "../types/validation";
|
|
26
31
|
import { waitForEvent } from "../utils/event-waiter";
|
|
32
|
+
import { PaymentClient, buildX402ResourceUrl } from "../payments/payment-client";
|
|
33
|
+
import type { SecurePrivateKey } from "../utils/secure-private-key";
|
|
27
34
|
|
|
28
35
|
export interface SendMessageOptions {
|
|
29
36
|
room: string;
|
|
@@ -39,6 +46,30 @@ export interface AgentCommand {
|
|
|
39
46
|
room: string;
|
|
40
47
|
}
|
|
41
48
|
|
|
49
|
+
/**
|
|
50
|
+
* Result of a quote request, containing pricing and agent details.
|
|
51
|
+
*/
|
|
52
|
+
export interface QuoteResult {
|
|
53
|
+
taskId: string;
|
|
54
|
+
agentId: string;
|
|
55
|
+
agentName: string;
|
|
56
|
+
agentWallet: string;
|
|
57
|
+
command: string;
|
|
58
|
+
pricing: PricingInfo;
|
|
59
|
+
expiresAt: Date;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
export interface MessageRouterConfig {
|
|
63
|
+
messageTimeout?: number;
|
|
64
|
+
responseFormat?: ResponseFormat;
|
|
65
|
+
autoApproveQuotes?: boolean;
|
|
66
|
+
maxPricePerRequest?: number;
|
|
67
|
+
quoteTimeout?: number;
|
|
68
|
+
wsUrl?: string;
|
|
69
|
+
paymentNetwork?: string;
|
|
70
|
+
paymentAsset?: string;
|
|
71
|
+
}
|
|
72
|
+
|
|
42
73
|
export class MessageRouter extends EventEmitter<SDKEvents> {
|
|
43
74
|
private readonly wsClient: WebSocketClient;
|
|
44
75
|
private readonly webhookHandler: WebhookHandler;
|
|
@@ -47,15 +78,22 @@ export class MessageRouter extends EventEmitter<SDKEvents> {
|
|
|
47
78
|
private readonly messageTimeout: number;
|
|
48
79
|
private readonly responseFormat: ResponseFormat;
|
|
49
80
|
|
|
81
|
+
// Quote-approve flow (v2.2.0)
|
|
82
|
+
private paymentClient: PaymentClient | null = null;
|
|
83
|
+
private readonly pendingQuotes: Map<string, QuoteResult> = new Map();
|
|
84
|
+
private readonly autoApproveQuotes: boolean;
|
|
85
|
+
private readonly maxPricePerRequest?: number;
|
|
86
|
+
private readonly quoteTimeout: number;
|
|
87
|
+
private readonly wsUrl: string;
|
|
88
|
+
private readonly paymentNetwork: string;
|
|
89
|
+
private readonly paymentAsset: string;
|
|
90
|
+
|
|
50
91
|
constructor(
|
|
51
92
|
wsClient: WebSocketClient,
|
|
52
93
|
webhookHandler: WebhookHandler,
|
|
53
94
|
responseFormatter: ResponseFormatter,
|
|
54
95
|
logger: Logger,
|
|
55
|
-
config:
|
|
56
|
-
messageTimeout?: number;
|
|
57
|
-
responseFormat?: ResponseFormat;
|
|
58
|
-
}
|
|
96
|
+
config: MessageRouterConfig
|
|
59
97
|
) {
|
|
60
98
|
super();
|
|
61
99
|
this.wsClient = wsClient;
|
|
@@ -65,9 +103,29 @@ export class MessageRouter extends EventEmitter<SDKEvents> {
|
|
|
65
103
|
this.messageTimeout = config.messageTimeout ?? TIMEOUTS.DEFAULT_MESSAGE_TIMEOUT;
|
|
66
104
|
this.responseFormat = config.responseFormat ?? "humanized";
|
|
67
105
|
|
|
106
|
+
// Quote-approve config (v2.2.0)
|
|
107
|
+
this.autoApproveQuotes = config.autoApproveQuotes ?? true;
|
|
108
|
+
this.maxPricePerRequest = config.maxPricePerRequest;
|
|
109
|
+
this.quoteTimeout = config.quoteTimeout ?? 30000;
|
|
110
|
+
this.wsUrl = config.wsUrl ?? "";
|
|
111
|
+
this.paymentNetwork = config.paymentNetwork ?? "eip155:3338";
|
|
112
|
+
// USDC contract address on PEAQ network
|
|
113
|
+
this.paymentAsset = config.paymentAsset ?? "0xbbA60da06c2c5424f03f7434542280FCAd453d10";
|
|
114
|
+
|
|
68
115
|
this.setupEventForwarding();
|
|
69
116
|
}
|
|
70
117
|
|
|
118
|
+
/**
|
|
119
|
+
* Sets up the payment client for quote-approve flow.
|
|
120
|
+
* Must be called before using requestQuote/confirmQuote with paid tasks.
|
|
121
|
+
*/
|
|
122
|
+
public setPaymentClient(secureKey: SecurePrivateKey, walletAddress: string): void {
|
|
123
|
+
this.paymentClient = new PaymentClient(secureKey, walletAddress, {
|
|
124
|
+
network: this.paymentNetwork,
|
|
125
|
+
asset: this.paymentAsset
|
|
126
|
+
});
|
|
127
|
+
}
|
|
128
|
+
|
|
71
129
|
/**
|
|
72
130
|
* Sends a message to agents via the coordinator.
|
|
73
131
|
* The coordinator intelligently selects the most appropriate agent.
|
|
@@ -107,21 +165,31 @@ export class MessageRouter extends EventEmitter<SDKEvents> {
|
|
|
107
165
|
throw new SDKError("Not connected to Teneo network", ErrorCode.NOT_CONNECTED);
|
|
108
166
|
}
|
|
109
167
|
|
|
110
|
-
// Validate content
|
|
111
168
|
const validatedContent = MessageContentSchema.parse(content);
|
|
112
|
-
|
|
113
169
|
const room = options.room;
|
|
114
170
|
if (!room) {
|
|
115
171
|
throw new ValidationError("Room parameter is required");
|
|
116
172
|
}
|
|
117
173
|
|
|
118
|
-
// Use
|
|
174
|
+
// Use quote-approve flow with auto-approval (v2.2.0)
|
|
175
|
+
if (this.autoApproveQuotes) {
|
|
176
|
+
this.logger.debug("MessageRouter: Using quote-approve flow", {
|
|
177
|
+
content: validatedContent,
|
|
178
|
+
room
|
|
179
|
+
});
|
|
180
|
+
const quote = await this.requestQuote(validatedContent, room);
|
|
181
|
+
return await this.confirmQuote(quote.taskId, {
|
|
182
|
+
waitForResponse: options.waitForResponse,
|
|
183
|
+
timeout: options.timeout ?? this.messageTimeout
|
|
184
|
+
});
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
// Legacy flow (auto-approval disabled - user must manually confirm quotes)
|
|
119
188
|
const authState = this.wsClient.getAuthState();
|
|
120
189
|
const fromAddress = options.from ?? authState.walletAddress;
|
|
121
|
-
|
|
122
190
|
const message = createUserMessage(validatedContent, room, fromAddress);
|
|
123
191
|
|
|
124
|
-
this.logger.debug("MessageRouter: Sending message", {
|
|
192
|
+
this.logger.debug("MessageRouter: Sending message (legacy)", {
|
|
125
193
|
content: validatedContent,
|
|
126
194
|
room,
|
|
127
195
|
from: fromAddress
|
|
@@ -164,7 +232,6 @@ export class MessageRouter extends EventEmitter<SDKEvents> {
|
|
|
164
232
|
throw new SDKError("Not connected to Teneo network", ErrorCode.NOT_CONNECTED);
|
|
165
233
|
}
|
|
166
234
|
|
|
167
|
-
// Validate command
|
|
168
235
|
const validatedAgent = AgentIdSchema.parse(command.agent);
|
|
169
236
|
const validatedCommand = AgentCommandContentSchema.parse(command.command);
|
|
170
237
|
|
|
@@ -173,15 +240,24 @@ export class MessageRouter extends EventEmitter<SDKEvents> {
|
|
|
173
240
|
throw new ValidationError("Room parameter is required");
|
|
174
241
|
}
|
|
175
242
|
|
|
176
|
-
|
|
243
|
+
const content = `@${validatedAgent} ${validatedCommand}`;
|
|
244
|
+
|
|
245
|
+
// Use quote-approve flow with auto-approval (v2.2.0)
|
|
246
|
+
if (this.autoApproveQuotes) {
|
|
247
|
+
this.logger.debug("MessageRouter: Using quote-approve flow", { content, room });
|
|
248
|
+
const quote = await this.requestQuote(content, room);
|
|
249
|
+
return await this.confirmQuote(quote.taskId, {
|
|
250
|
+
waitForResponse,
|
|
251
|
+
timeout: this.messageTimeout
|
|
252
|
+
});
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
// Legacy flow (auto-approval disabled - user must manually confirm quotes)
|
|
177
256
|
const authState = this.wsClient.getAuthState();
|
|
178
257
|
const walletAddress = authState.walletAddress;
|
|
179
|
-
|
|
180
|
-
// Format as direct command
|
|
181
|
-
const content = `@${validatedAgent} ${validatedCommand}`;
|
|
182
258
|
const message = createUserMessage(content, room, walletAddress);
|
|
183
259
|
|
|
184
|
-
this.logger.debug("MessageRouter: Sending direct command", {
|
|
260
|
+
this.logger.debug("MessageRouter: Sending direct command (legacy)", {
|
|
185
261
|
agent: validatedAgent,
|
|
186
262
|
command: validatedCommand,
|
|
187
263
|
room,
|
|
@@ -204,6 +280,128 @@ export class MessageRouter extends EventEmitter<SDKEvents> {
|
|
|
204
280
|
}
|
|
205
281
|
}
|
|
206
282
|
|
|
283
|
+
/**
|
|
284
|
+
* Requests a quote for a task without auto-approval.
|
|
285
|
+
* Returns the quote data for manual confirmation.
|
|
286
|
+
*/
|
|
287
|
+
public async requestQuote(content: string, room: string): Promise<QuoteResult> {
|
|
288
|
+
if (!this.wsClient.isConnected) {
|
|
289
|
+
throw new SDKError("Not connected to Teneo network", ErrorCode.NOT_CONNECTED);
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
const message = createRequestTask(content, room);
|
|
293
|
+
this.logger.debug("MessageRouter: Requesting quote", { content, room });
|
|
294
|
+
|
|
295
|
+
await this.wsClient.sendMessage(message);
|
|
296
|
+
|
|
297
|
+
const quote = await waitForEvent<TaskQuoteMessage>(this.wsClient, "quote:received", {
|
|
298
|
+
timeout: this.quoteTimeout,
|
|
299
|
+
timeoutMessage: `Quote request timed out after ${this.quoteTimeout}ms`
|
|
300
|
+
});
|
|
301
|
+
|
|
302
|
+
const result: QuoteResult = {
|
|
303
|
+
taskId: quote.data.task_id,
|
|
304
|
+
agentId: quote.data.agent_id,
|
|
305
|
+
agentName: quote.data.agent_name,
|
|
306
|
+
agentWallet: quote.data.agent_wallet,
|
|
307
|
+
command: quote.data.command,
|
|
308
|
+
pricing: quote.data.pricing,
|
|
309
|
+
expiresAt: new Date(quote.data.expires_at)
|
|
310
|
+
};
|
|
311
|
+
|
|
312
|
+
this.pendingQuotes.set(result.taskId, result);
|
|
313
|
+
return result;
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
/**
|
|
317
|
+
* Confirms a quote and executes the task with payment.
|
|
318
|
+
* Can optionally wait for the task response.
|
|
319
|
+
*/
|
|
320
|
+
public async confirmQuote(
|
|
321
|
+
taskId: string,
|
|
322
|
+
options?: { waitForResponse?: boolean; timeout?: number }
|
|
323
|
+
): Promise<FormattedResponse | void> {
|
|
324
|
+
if (!this.wsClient.isConnected) {
|
|
325
|
+
throw new SDKError("Not connected to Teneo network", ErrorCode.NOT_CONNECTED);
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
const quote = this.pendingQuotes.get(taskId);
|
|
329
|
+
if (!quote) {
|
|
330
|
+
throw new ValidationError(`No pending quote found for task ${taskId}`);
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
if (new Date() > quote.expiresAt) {
|
|
334
|
+
this.pendingQuotes.delete(taskId);
|
|
335
|
+
throw new SDKError("Quote has expired", ErrorCode.QUOTE_EXPIRED);
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
// Check price limit
|
|
339
|
+
if (this.maxPricePerRequest !== undefined) {
|
|
340
|
+
const priceInUnits = quote.pricing.pricePerUnit * 1_000_000;
|
|
341
|
+
if (priceInUnits > this.maxPricePerRequest) {
|
|
342
|
+
this.emit("payment:blocked", {
|
|
343
|
+
agentId: quote.agentId,
|
|
344
|
+
agentPrice: priceInUnits,
|
|
345
|
+
maxPrice: this.maxPricePerRequest
|
|
346
|
+
});
|
|
347
|
+
throw new PaymentError(
|
|
348
|
+
`Quote price exceeds limit: ${priceInUnits} > ${this.maxPricePerRequest}`,
|
|
349
|
+
ErrorCode.PRICE_LIMIT_EXCEEDED,
|
|
350
|
+
{ agentId: quote.agentId, agentPrice: priceInUnits, maxPrice: this.maxPricePerRequest }
|
|
351
|
+
);
|
|
352
|
+
}
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
let paymentHeader: string | undefined;
|
|
356
|
+
|
|
357
|
+
// Create payment header if payment client is configured and price > 0
|
|
358
|
+
if (this.paymentClient && quote.pricing.pricePerUnit > 0) {
|
|
359
|
+
try {
|
|
360
|
+
paymentHeader = await this.paymentClient.createPaymentHeader(
|
|
361
|
+
quote.pricing.pricePerUnit * 1_000_000,
|
|
362
|
+
quote.agentWallet,
|
|
363
|
+
buildX402ResourceUrl(this.wsUrl)
|
|
364
|
+
);
|
|
365
|
+
this.emit("payment:attached", {
|
|
366
|
+
agentId: quote.agentId,
|
|
367
|
+
amount: quote.pricing.pricePerUnit * 1_000_000,
|
|
368
|
+
command: quote.command
|
|
369
|
+
});
|
|
370
|
+
} catch (error) {
|
|
371
|
+
this.logger.error("Failed to create payment header for quote confirmation", error);
|
|
372
|
+
throw new PaymentError("Failed to create payment", ErrorCode.PAYMENT_FAILED, {
|
|
373
|
+
agentId: quote.agentId
|
|
374
|
+
});
|
|
375
|
+
}
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
const confirmMessage = createConfirmTask(taskId, paymentHeader);
|
|
379
|
+
|
|
380
|
+
this.logger.debug("MessageRouter: Confirming quote", { taskId });
|
|
381
|
+
|
|
382
|
+
await this.wsClient.sendMessage(confirmMessage);
|
|
383
|
+
|
|
384
|
+
// Delete quote only after successful send (enables retry on network failure)
|
|
385
|
+
this.pendingQuotes.delete(taskId);
|
|
386
|
+
|
|
387
|
+
if (options?.waitForResponse) {
|
|
388
|
+
const timeout = options.timeout ?? this.messageTimeout;
|
|
389
|
+
const response = await waitForEvent<AgentResponse>(this.wsClient, "agent:response", {
|
|
390
|
+
timeout,
|
|
391
|
+
filter: (r) => r.taskId === taskId,
|
|
392
|
+
timeoutMessage: `Task response timed out after ${timeout}ms (taskId: ${taskId})`
|
|
393
|
+
});
|
|
394
|
+
return response as FormattedResponse;
|
|
395
|
+
}
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
/**
|
|
399
|
+
* Gets a pending quote by task ID.
|
|
400
|
+
*/
|
|
401
|
+
public getPendingQuote(taskId: string): QuoteResult | undefined {
|
|
402
|
+
return this.pendingQuotes.get(taskId);
|
|
403
|
+
}
|
|
404
|
+
|
|
207
405
|
/**
|
|
208
406
|
* Send message and wait for agent response
|
|
209
407
|
* Uses event-waiter utility for clean Promise-based waiting with automatic cleanup
|
|
@@ -58,7 +58,7 @@ export class RoomManagementManager extends EventEmitter<SDKEvents> {
|
|
|
58
58
|
*/
|
|
59
59
|
public async createRoom(options: CreateRoomOptions): Promise<RoomInfo> {
|
|
60
60
|
if (!this.wsClient.isConnected) {
|
|
61
|
-
throw new SDKError("Not connected to Teneo
|
|
61
|
+
throw new SDKError("Not connected to Teneo Protocol", ErrorCode.NOT_CONNECTED);
|
|
62
62
|
}
|
|
63
63
|
|
|
64
64
|
// Validate inputs
|
|
@@ -135,7 +135,7 @@ export class RoomManagementManager extends EventEmitter<SDKEvents> {
|
|
|
135
135
|
*/
|
|
136
136
|
public async updateRoom(roomId: string, updates: UpdateRoomOptions): Promise<RoomInfo> {
|
|
137
137
|
if (!this.wsClient.isConnected) {
|
|
138
|
-
throw new SDKError("Not connected to Teneo
|
|
138
|
+
throw new SDKError("Not connected to Teneo Protocol", ErrorCode.NOT_CONNECTED);
|
|
139
139
|
}
|
|
140
140
|
|
|
141
141
|
// Verify user owns room
|
|
@@ -224,7 +224,7 @@ export class RoomManagementManager extends EventEmitter<SDKEvents> {
|
|
|
224
224
|
*/
|
|
225
225
|
public async deleteRoom(roomId: string): Promise<void> {
|
|
226
226
|
if (!this.wsClient.isConnected) {
|
|
227
|
-
throw new SDKError("Not connected to Teneo
|
|
227
|
+
throw new SDKError("Not connected to Teneo Protocol", ErrorCode.NOT_CONNECTED);
|
|
228
228
|
}
|
|
229
229
|
|
|
230
230
|
// Verify user owns room
|
|
@@ -505,10 +505,7 @@ export class RoomManagementManager extends EventEmitter<SDKEvents> {
|
|
|
505
505
|
throw new SDKError("Room name cannot be empty", ErrorCode.VALIDATION_ERROR);
|
|
506
506
|
}
|
|
507
507
|
if (name.length > 100) {
|
|
508
|
-
throw new SDKError(
|
|
509
|
-
"Room name too long (max 100 characters)",
|
|
510
|
-
ErrorCode.VALIDATION_ERROR
|
|
511
|
-
);
|
|
508
|
+
throw new SDKError("Room name too long (max 100 characters)", ErrorCode.VALIDATION_ERROR);
|
|
512
509
|
}
|
|
513
510
|
}
|
|
514
511
|
|
|
@@ -5,13 +5,7 @@
|
|
|
5
5
|
|
|
6
6
|
import { EventEmitter } from "eventemitter3";
|
|
7
7
|
import { WebSocketClient } from "../core/websocket-client";
|
|
8
|
-
import {
|
|
9
|
-
createSubscribe,
|
|
10
|
-
createUnsubscribe,
|
|
11
|
-
createListRooms,
|
|
12
|
-
Logger,
|
|
13
|
-
RoomInfo
|
|
14
|
-
} from "../types";
|
|
8
|
+
import { createSubscribe, createUnsubscribe, createListRooms, Logger, RoomInfo } from "../types";
|
|
15
9
|
import { SDKEvents, SDKError } from "../types/events";
|
|
16
10
|
import { ErrorCode } from "../types/error-codes";
|
|
17
11
|
import { RoomIdSchema } from "../types/validation";
|
|
@@ -29,7 +23,7 @@ export class RoomManager extends EventEmitter<SDKEvents> {
|
|
|
29
23
|
}
|
|
30
24
|
|
|
31
25
|
/**
|
|
32
|
-
* Subscribes to a public room in the Teneo
|
|
26
|
+
* Subscribes to a public room in the Teneo Protocol.
|
|
33
27
|
* Validates room ID and sends subscribe message to the server.
|
|
34
28
|
* The actual subscription state is updated when the server confirms via room:subscribed event.
|
|
35
29
|
*
|
|
@@ -46,7 +40,7 @@ export class RoomManager extends EventEmitter<SDKEvents> {
|
|
|
46
40
|
*/
|
|
47
41
|
public async subscribeToRoom(roomId: string): Promise<void> {
|
|
48
42
|
if (!this.wsClient.isConnected) {
|
|
49
|
-
throw new SDKError("Not connected to Teneo
|
|
43
|
+
throw new SDKError("Not connected to Teneo Protocol", ErrorCode.NOT_CONNECTED);
|
|
50
44
|
}
|
|
51
45
|
|
|
52
46
|
// Validate room ID
|
|
@@ -59,24 +53,26 @@ export class RoomManager extends EventEmitter<SDKEvents> {
|
|
|
59
53
|
}
|
|
60
54
|
|
|
61
55
|
/**
|
|
62
|
-
* Unsubscribes from a room in the Teneo
|
|
56
|
+
* Unsubscribes from a public room in the Teneo Protocol.
|
|
63
57
|
* Validates room ID and sends unsubscribe message to the server.
|
|
64
58
|
* The actual subscription state is updated when the server confirms via room:unsubscribed event.
|
|
65
59
|
*
|
|
66
|
-
*
|
|
60
|
+
* Note: This only applies to public rooms. Private rooms cannot be unsubscribed from.
|
|
61
|
+
*
|
|
62
|
+
* @param roomId - The ID of the public room to unsubscribe from
|
|
67
63
|
* @returns Promise that resolves when unsubscribed
|
|
68
64
|
* @throws {SDKError} If not connected to the network
|
|
69
65
|
* @throws {ValidationError} If roomId is empty or invalid
|
|
70
66
|
*
|
|
71
67
|
* @example
|
|
72
68
|
* ```typescript
|
|
73
|
-
* await roomManager.unsubscribeFromRoom('
|
|
74
|
-
* console.log('Unsubscription request sent for
|
|
69
|
+
* await roomManager.unsubscribeFromRoom('public-announcements');
|
|
70
|
+
* console.log('Unsubscription request sent for public room');
|
|
75
71
|
* ```
|
|
76
72
|
*/
|
|
77
73
|
public async unsubscribeFromRoom(roomId: string): Promise<void> {
|
|
78
74
|
if (!this.wsClient.isConnected) {
|
|
79
|
-
throw new SDKError("Not connected to Teneo
|
|
75
|
+
throw new SDKError("Not connected to Teneo Protocol", ErrorCode.NOT_CONNECTED);
|
|
80
76
|
}
|
|
81
77
|
|
|
82
78
|
// Validate room ID
|
|
@@ -185,7 +181,7 @@ export class RoomManager extends EventEmitter<SDKEvents> {
|
|
|
185
181
|
*/
|
|
186
182
|
public async listRooms(): Promise<RoomInfo[]> {
|
|
187
183
|
if (!this.wsClient.isConnected) {
|
|
188
|
-
throw new SDKError("Not connected to Teneo
|
|
184
|
+
throw new SDKError("Not connected to Teneo Protocol", ErrorCode.NOT_CONNECTED);
|
|
189
185
|
}
|
|
190
186
|
|
|
191
187
|
this.logger.info("RoomManager: Listing rooms");
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Payment module exports for quote-approve flow (v2.2.0)
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
export {
|
|
6
|
+
PaymentClient,
|
|
7
|
+
type PaymentClientConfig,
|
|
8
|
+
// Constants
|
|
9
|
+
USDC_CONTRACT,
|
|
10
|
+
PEAQ_CHAIN_ID,
|
|
11
|
+
USDC_DECIMALS,
|
|
12
|
+
X402_VERSION,
|
|
13
|
+
DEFAULT_PAYMENT_TIMEOUT_SECONDS,
|
|
14
|
+
DEFAULT_PAY_TO_ADDRESS,
|
|
15
|
+
DEFAULT_RPC_URL,
|
|
16
|
+
// Utilities
|
|
17
|
+
buildX402ResourceUrl,
|
|
18
|
+
usdcToUnits,
|
|
19
|
+
unitsToUsdc,
|
|
20
|
+
// Types
|
|
21
|
+
type SupportedChain
|
|
22
|
+
} from "./payment-client";
|
|
@@ -0,0 +1,240 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* PaymentClient - Handles x402 payment header generation for quote-approve flow
|
|
3
|
+
*
|
|
4
|
+
* Creates x402 V2 payment headers for USDC payments on PEAQ network.
|
|
5
|
+
* Implements ERC-3009 TransferWithAuthorization signing using viem's EIP-712.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { defineChain, type Hex, toHex } from "viem";
|
|
9
|
+
import { privateKeyToAccount, signTypedData } from "viem/accounts";
|
|
10
|
+
import type { SecurePrivateKey } from "../utils/secure-private-key";
|
|
11
|
+
|
|
12
|
+
// PEAQ chain definition
|
|
13
|
+
const peaq = defineChain({
|
|
14
|
+
id: 3338,
|
|
15
|
+
name: "peaq",
|
|
16
|
+
network: "peaq",
|
|
17
|
+
nativeCurrency: {
|
|
18
|
+
decimals: 18,
|
|
19
|
+
name: "PEAQ",
|
|
20
|
+
symbol: "PEAQ"
|
|
21
|
+
},
|
|
22
|
+
rpcUrls: {
|
|
23
|
+
default: {
|
|
24
|
+
http: ["https://peaq.api.onfinality.io/public"]
|
|
25
|
+
},
|
|
26
|
+
public: {
|
|
27
|
+
http: ["https://peaq.api.onfinality.io/public"]
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
// USDC contract on PEAQ
|
|
33
|
+
const USDC_CONTRACT = "0xbbA60da06c2c5424f03f7434542280FCAd453d10";
|
|
34
|
+
const PEAQ_NETWORK_CAIP2 = "eip155:3338";
|
|
35
|
+
|
|
36
|
+
// ERC-3009 TransferWithAuthorization EIP-712 types
|
|
37
|
+
const ERC3009_TYPES = {
|
|
38
|
+
TransferWithAuthorization: [
|
|
39
|
+
{ name: "from", type: "address" },
|
|
40
|
+
{ name: "to", type: "address" },
|
|
41
|
+
{ name: "value", type: "uint256" },
|
|
42
|
+
{ name: "validAfter", type: "uint256" },
|
|
43
|
+
{ name: "validBefore", type: "uint256" },
|
|
44
|
+
{ name: "nonce", type: "bytes32" }
|
|
45
|
+
]
|
|
46
|
+
} as const;
|
|
47
|
+
|
|
48
|
+
export interface PaymentClientConfig {
|
|
49
|
+
network?: string; // CAIP-2 format, default: "eip155:3338"
|
|
50
|
+
asset?: string; // Asset contract, default: USDC on PEAQ
|
|
51
|
+
resourceUrl?: string; // x402 resource URL
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
// x402 constants
|
|
55
|
+
export const USDC_DECIMALS = 6;
|
|
56
|
+
export const X402_VERSION = 2;
|
|
57
|
+
export const DEFAULT_PAYMENT_TIMEOUT_SECONDS = 60;
|
|
58
|
+
export const DEFAULT_PAY_TO_ADDRESS = "0x0000000000000000000000000000000000000000";
|
|
59
|
+
export const DEFAULT_RPC_URL = "https://peaq.api.onfinality.io/public";
|
|
60
|
+
export type SupportedChain = "peaq";
|
|
61
|
+
|
|
62
|
+
// Re-export constants for external use
|
|
63
|
+
export { USDC_CONTRACT, PEAQ_NETWORK_CAIP2 as PEAQ_CHAIN_ID };
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* Converts a WebSocket URL to an HTTP(S) URL for x402 resource specification.
|
|
67
|
+
* The x402 protocol requires HTTP URLs, not WebSocket URLs.
|
|
68
|
+
*
|
|
69
|
+
* @param wsUrl - WebSocket URL (wss:// or ws://)
|
|
70
|
+
* @returns HTTP URL (https:// or http://)
|
|
71
|
+
*
|
|
72
|
+
* @example
|
|
73
|
+
* buildX402ResourceUrl("wss://api.teneo.com/ws") // "https://api.teneo.com/x402"
|
|
74
|
+
*/
|
|
75
|
+
export function buildX402ResourceUrl(wsUrl: string): string {
|
|
76
|
+
const httpUrl = wsUrl.replace(/^wss:\/\//, "https://").replace(/^ws:\/\//, "http://");
|
|
77
|
+
|
|
78
|
+
// Replace /ws endpoint with /x402, or append /x402 if URL doesn't end with /ws
|
|
79
|
+
return /\/ws\/?$/.test(httpUrl)
|
|
80
|
+
? httpUrl.replace(/\/ws\/?$/, "/x402")
|
|
81
|
+
: httpUrl.replace(/\/?$/, "/x402");
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* Converts USDC human-readable amount to micro-units (6 decimals).
|
|
86
|
+
* @param usdc - Amount in USDC (e.g., 1.5)
|
|
87
|
+
* @returns Amount in micro-units (e.g., 1500000)
|
|
88
|
+
*/
|
|
89
|
+
export function usdcToUnits(usdc: number): number {
|
|
90
|
+
return Math.round(usdc * 10 ** USDC_DECIMALS);
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* Converts micro-units to USDC human-readable amount.
|
|
95
|
+
* @param units - Amount in micro-units (e.g., 1500000)
|
|
96
|
+
* @returns Amount in USDC (e.g., 1.5)
|
|
97
|
+
*/
|
|
98
|
+
export function unitsToUsdc(units: number): number {
|
|
99
|
+
return units / 10 ** USDC_DECIMALS;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
/**
|
|
103
|
+
* Generate a random 32-byte nonce as hex string
|
|
104
|
+
*/
|
|
105
|
+
function generateNonce(): Hex {
|
|
106
|
+
const randomBytes = new Uint8Array(32);
|
|
107
|
+
crypto.getRandomValues(randomBytes);
|
|
108
|
+
return toHex(randomBytes);
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
/**
|
|
112
|
+
* PaymentClient handles creation of x402 V2 payment headers
|
|
113
|
+
* for the quote-approve payment flow.
|
|
114
|
+
*/
|
|
115
|
+
export class PaymentClient {
|
|
116
|
+
private readonly secureKey: SecurePrivateKey;
|
|
117
|
+
private readonly walletAddress: string;
|
|
118
|
+
private readonly network: string;
|
|
119
|
+
private readonly asset: string;
|
|
120
|
+
private readonly resourceUrl: string;
|
|
121
|
+
|
|
122
|
+
constructor(
|
|
123
|
+
secureKey: SecurePrivateKey,
|
|
124
|
+
walletAddress: string,
|
|
125
|
+
config: PaymentClientConfig = {}
|
|
126
|
+
) {
|
|
127
|
+
this.secureKey = secureKey;
|
|
128
|
+
this.walletAddress = walletAddress;
|
|
129
|
+
this.network = config.network ?? PEAQ_NETWORK_CAIP2;
|
|
130
|
+
this.asset = config.asset ?? USDC_CONTRACT;
|
|
131
|
+
this.resourceUrl = config.resourceUrl ?? "";
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
/**
|
|
135
|
+
* Creates an x402 V2 payment header for a specific amount and recipient.
|
|
136
|
+
*
|
|
137
|
+
* @param amountMicroUnits - Amount in micro-units (e.g., 1000 = 0.001 USDC)
|
|
138
|
+
* @param recipientAddress - Wallet address of the payment recipient (agent)
|
|
139
|
+
* @param resourceUrl - Optional override for x402 resource URL
|
|
140
|
+
* @returns Base64 encoded x402 V2 payment header
|
|
141
|
+
*/
|
|
142
|
+
async createPaymentHeader(
|
|
143
|
+
amountMicroUnits: number,
|
|
144
|
+
recipientAddress: string,
|
|
145
|
+
resourceUrl?: string
|
|
146
|
+
): Promise<string> {
|
|
147
|
+
const resource = resourceUrl || this.resourceUrl || this.getDefaultResourceUrl();
|
|
148
|
+
const amountStr = Math.round(amountMicroUnits).toString();
|
|
149
|
+
|
|
150
|
+
// Create payment header using the secure key
|
|
151
|
+
const header = await this.secureKey.use(async (privateKey) => {
|
|
152
|
+
const account = privateKeyToAccount(privateKey as `0x${string}`);
|
|
153
|
+
|
|
154
|
+
// Time bounds for the authorization (valid for 60 seconds)
|
|
155
|
+
const now = Math.floor(Date.now() / 1000);
|
|
156
|
+
const validAfter = now - 60; // Valid from 60 seconds ago
|
|
157
|
+
const validBefore = now + 60; // Valid until 60 seconds from now
|
|
158
|
+
const nonce = generateNonce();
|
|
159
|
+
|
|
160
|
+
// EIP-712 domain for USDC on PEAQ
|
|
161
|
+
const domain = {
|
|
162
|
+
name: "USDC",
|
|
163
|
+
version: "2",
|
|
164
|
+
chainId: BigInt(peaq.id),
|
|
165
|
+
verifyingContract: this.asset as `0x${string}`
|
|
166
|
+
};
|
|
167
|
+
|
|
168
|
+
// ERC-3009 TransferWithAuthorization message
|
|
169
|
+
const message = {
|
|
170
|
+
from: account.address,
|
|
171
|
+
to: recipientAddress as `0x${string}`,
|
|
172
|
+
value: BigInt(amountStr),
|
|
173
|
+
validAfter: BigInt(validAfter),
|
|
174
|
+
validBefore: BigInt(validBefore),
|
|
175
|
+
nonce: nonce
|
|
176
|
+
};
|
|
177
|
+
|
|
178
|
+
// Sign the EIP-712 typed data
|
|
179
|
+
const signature = await signTypedData({
|
|
180
|
+
privateKey: privateKey as `0x${string}`,
|
|
181
|
+
domain,
|
|
182
|
+
types: ERC3009_TYPES,
|
|
183
|
+
primaryType: "TransferWithAuthorization",
|
|
184
|
+
message
|
|
185
|
+
});
|
|
186
|
+
|
|
187
|
+
// Build V1 payload (authorization data)
|
|
188
|
+
const v1Payload = {
|
|
189
|
+
authorization: {
|
|
190
|
+
from: account.address,
|
|
191
|
+
to: recipientAddress,
|
|
192
|
+
value: amountStr,
|
|
193
|
+
validAfter: validAfter.toString(),
|
|
194
|
+
validBefore: validBefore.toString(),
|
|
195
|
+
nonce: nonce
|
|
196
|
+
},
|
|
197
|
+
signature: signature
|
|
198
|
+
};
|
|
199
|
+
|
|
200
|
+
// Build V2 payload (what the backend expects)
|
|
201
|
+
const v2Payload = {
|
|
202
|
+
x402Version: 2,
|
|
203
|
+
resource: {
|
|
204
|
+
url: resource,
|
|
205
|
+
description: "Teneo SDK payment",
|
|
206
|
+
mimeType: "application/json"
|
|
207
|
+
},
|
|
208
|
+
accepted: {
|
|
209
|
+
scheme: "exact",
|
|
210
|
+
network: this.network,
|
|
211
|
+
amount: amountStr,
|
|
212
|
+
asset: this.asset,
|
|
213
|
+
payTo: recipientAddress,
|
|
214
|
+
maxTimeoutSeconds: 60,
|
|
215
|
+
extra: { name: "USDC", version: "2" }
|
|
216
|
+
},
|
|
217
|
+
payload: v1Payload
|
|
218
|
+
};
|
|
219
|
+
|
|
220
|
+
return Buffer.from(JSON.stringify(v2Payload)).toString("base64");
|
|
221
|
+
});
|
|
222
|
+
|
|
223
|
+
return header;
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
/**
|
|
227
|
+
* Get default x402 resource URL from WebSocket URL
|
|
228
|
+
*/
|
|
229
|
+
private getDefaultResourceUrl(): string {
|
|
230
|
+
// Default fallback
|
|
231
|
+
return "https://dev-rooms-websocket-ai-core-o9fmb.ondigitalocean.app/x402";
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
/**
|
|
235
|
+
* Gets the wallet address associated with this payment client.
|
|
236
|
+
*/
|
|
237
|
+
get address(): string {
|
|
238
|
+
return this.walletAddress;
|
|
239
|
+
}
|
|
240
|
+
}
|