@remitmd/sdk 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.
Files changed (132) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +250 -0
  3. package/dist/a2a.d.ts +137 -0
  4. package/dist/a2a.d.ts.map +1 -0
  5. package/dist/a2a.js +121 -0
  6. package/dist/a2a.js.map +1 -0
  7. package/dist/client.d.ts +41 -0
  8. package/dist/client.d.ts.map +1 -0
  9. package/dist/client.js +81 -0
  10. package/dist/client.js.map +1 -0
  11. package/dist/errors.d.ts +108 -0
  12. package/dist/errors.d.ts.map +1 -0
  13. package/dist/errors.js +218 -0
  14. package/dist/errors.js.map +1 -0
  15. package/dist/http.d.ts +23 -0
  16. package/dist/http.d.ts.map +1 -0
  17. package/dist/http.js +150 -0
  18. package/dist/http.js.map +1 -0
  19. package/dist/index.d.ts +18 -0
  20. package/dist/index.d.ts.map +1 -0
  21. package/dist/index.js +21 -0
  22. package/dist/index.js.map +1 -0
  23. package/dist/integrations/vercel-ai.d.ts +44 -0
  24. package/dist/integrations/vercel-ai.d.ts.map +1 -0
  25. package/dist/integrations/vercel-ai.js +175 -0
  26. package/dist/integrations/vercel-ai.js.map +1 -0
  27. package/dist/models/bounty.d.ts +22 -0
  28. package/dist/models/bounty.d.ts.map +1 -0
  29. package/dist/models/bounty.js +2 -0
  30. package/dist/models/bounty.js.map +1 -0
  31. package/dist/models/common.d.ts +78 -0
  32. package/dist/models/common.d.ts.map +1 -0
  33. package/dist/models/common.js +3 -0
  34. package/dist/models/common.js.map +1 -0
  35. package/dist/models/deposit.d.ts +13 -0
  36. package/dist/models/deposit.d.ts.map +1 -0
  37. package/dist/models/deposit.js +2 -0
  38. package/dist/models/deposit.js.map +1 -0
  39. package/dist/models/escrow.d.ts +16 -0
  40. package/dist/models/escrow.d.ts.map +1 -0
  41. package/dist/models/escrow.js +2 -0
  42. package/dist/models/escrow.js.map +1 -0
  43. package/dist/models/index.d.ts +9 -0
  44. package/dist/models/index.d.ts.map +1 -0
  45. package/dist/models/index.js +9 -0
  46. package/dist/models/index.js.map +1 -0
  47. package/dist/models/invoice.d.ts +30 -0
  48. package/dist/models/invoice.d.ts.map +1 -0
  49. package/dist/models/invoice.js +2 -0
  50. package/dist/models/invoice.js.map +1 -0
  51. package/dist/models/reputation.d.ts +7 -0
  52. package/dist/models/reputation.d.ts.map +1 -0
  53. package/dist/models/reputation.js +2 -0
  54. package/dist/models/reputation.js.map +1 -0
  55. package/dist/models/stream.d.ts +15 -0
  56. package/dist/models/stream.d.ts.map +1 -0
  57. package/dist/models/stream.js +2 -0
  58. package/dist/models/stream.js.map +1 -0
  59. package/dist/models/tab.d.ts +21 -0
  60. package/dist/models/tab.d.ts.map +1 -0
  61. package/dist/models/tab.js +2 -0
  62. package/dist/models/tab.js.map +1 -0
  63. package/dist/provider.d.ts +135 -0
  64. package/dist/provider.d.ts.map +1 -0
  65. package/dist/provider.js +218 -0
  66. package/dist/provider.js.map +1 -0
  67. package/dist/signer.d.ts +31 -0
  68. package/dist/signer.d.ts.map +1 -0
  69. package/dist/signer.js +35 -0
  70. package/dist/signer.js.map +1 -0
  71. package/dist/testing/local.d.ts +31 -0
  72. package/dist/testing/local.d.ts.map +1 -0
  73. package/dist/testing/local.js +100 -0
  74. package/dist/testing/local.js.map +1 -0
  75. package/dist/testing/mock.d.ts +95 -0
  76. package/dist/testing/mock.d.ts.map +1 -0
  77. package/dist/testing/mock.js +407 -0
  78. package/dist/testing/mock.js.map +1 -0
  79. package/dist/wallet.d.ts +162 -0
  80. package/dist/wallet.d.ts.map +1 -0
  81. package/dist/wallet.js +365 -0
  82. package/dist/wallet.js.map +1 -0
  83. package/dist/x402.d.ts +78 -0
  84. package/dist/x402.d.ts.map +1 -0
  85. package/dist/x402.js +151 -0
  86. package/dist/x402.js.map +1 -0
  87. package/eslint.config.js +27 -0
  88. package/package.json +39 -0
  89. package/src/a2a.ts +241 -0
  90. package/src/client.ts +104 -0
  91. package/src/errors.ts +261 -0
  92. package/src/http.ts +190 -0
  93. package/src/index.ts +94 -0
  94. package/src/integrations/vercel-ai.ts +213 -0
  95. package/src/models/bounty.ts +23 -0
  96. package/src/models/common.ts +106 -0
  97. package/src/models/deposit.ts +13 -0
  98. package/src/models/escrow.ts +16 -0
  99. package/src/models/index.ts +8 -0
  100. package/src/models/invoice.ts +32 -0
  101. package/src/models/reputation.ts +7 -0
  102. package/src/models/stream.ts +15 -0
  103. package/src/models/tab.ts +22 -0
  104. package/src/provider.ts +281 -0
  105. package/src/signer.ts +70 -0
  106. package/src/testing/local.ts +118 -0
  107. package/src/testing/mock.ts +507 -0
  108. package/src/wallet.ts +546 -0
  109. package/src/x402.ts +202 -0
  110. package/tests/acceptance/bounty.test.ts +82 -0
  111. package/tests/acceptance/deposit.test.ts +70 -0
  112. package/tests/acceptance/direct.test.ts +53 -0
  113. package/tests/acceptance/escrow.test.ts +67 -0
  114. package/tests/acceptance/setup.ts +113 -0
  115. package/tests/acceptance/stream.test.ts +98 -0
  116. package/tests/acceptance/tab.test.ts +108 -0
  117. package/tests/acceptance/x402.test.ts +140 -0
  118. package/tests/compliance/auth.ts +69 -0
  119. package/tests/compliance/escrows.ts +96 -0
  120. package/tests/compliance/helpers.ts +90 -0
  121. package/tests/compliance/payments.ts +69 -0
  122. package/tests/compliance/tabs.ts +52 -0
  123. package/tests/test_a2a.ts +151 -0
  124. package/tests/test_errors.ts +80 -0
  125. package/tests/test_golden_vectors.ts +162 -0
  126. package/tests/test_integrations.ts +115 -0
  127. package/tests/test_mock.ts +217 -0
  128. package/tests/test_permit.ts +216 -0
  129. package/tests/test_provider.ts +304 -0
  130. package/tests/test_wallet.ts +108 -0
  131. package/tests/test_x402.ts +302 -0
  132. package/tsconfig.json +19 -0
package/dist/x402.js ADDED
@@ -0,0 +1,151 @@
1
+ /**
2
+ * x402 client middleware for auto-paying HTTP 402 Payment Required responses.
3
+ *
4
+ * x402 is an open payment standard where resource servers return HTTP 402 with
5
+ * a `PAYMENT-REQUIRED` header describing the cost. This module provides a
6
+ * `fetch` wrapper that intercepts those responses, signs an EIP-3009
7
+ * authorization, and retries the request with a `PAYMENT-SIGNATURE` header.
8
+ *
9
+ * Usage:
10
+ * ```typescript
11
+ * import { PrivateKeySigner } from "@remitmd/sdk";
12
+ * import { X402Client } from "@remitmd/sdk/x402";
13
+ *
14
+ * const signer = new PrivateKeySigner("0x...");
15
+ * const client = new X402Client({
16
+ * signer,
17
+ * address: signer.getAddress(),
18
+ * maxAutoPayUsdc: 0.10,
19
+ * });
20
+ *
21
+ * const response = await client.fetch("https://api.provider.com/v1/data");
22
+ * ```
23
+ */
24
+ import { randomBytes } from "node:crypto";
25
+ /** EIP-712 type definitions for USDC's transferWithAuthorization (EIP-3009). */
26
+ const EIP3009_TYPES = {
27
+ TransferWithAuthorization: [
28
+ { name: "from", type: "address" },
29
+ { name: "to", type: "address" },
30
+ { name: "value", type: "uint256" },
31
+ { name: "validAfter", type: "uint256" },
32
+ { name: "validBefore", type: "uint256" },
33
+ { name: "nonce", type: "bytes32" },
34
+ ],
35
+ };
36
+ /** Raised when an x402 payment amount exceeds the configured auto-pay limit. */
37
+ export class AllowanceExceededError extends Error {
38
+ amountUsdc;
39
+ limitUsdc;
40
+ constructor(amountUsdc, limitUsdc) {
41
+ super(`x402 payment ${amountUsdc.toFixed(6)} USDC exceeds auto-pay limit ${limitUsdc.toFixed(6)} USDC`);
42
+ this.name = "AllowanceExceededError";
43
+ this.amountUsdc = amountUsdc;
44
+ this.limitUsdc = limitUsdc;
45
+ }
46
+ }
47
+ /**
48
+ * `fetch` wrapper that auto-handles HTTP 402 Payment Required responses.
49
+ *
50
+ * On receiving a 402, the client:
51
+ * 1. Decodes the `PAYMENT-REQUIRED` header (base64 JSON)
52
+ * 2. Checks the amount is within `maxAutoPayUsdc`
53
+ * 3. Builds and signs an EIP-3009 `transferWithAuthorization`
54
+ * 4. Base64-encodes the `PAYMENT-SIGNATURE` header
55
+ * 5. Retries the original request with payment attached
56
+ *
57
+ * V2: The decoded `PAYMENT-REQUIRED` may include `resource`, `description`,
58
+ * and `mimeType` fields. Access the last payment via `lastPayment`.
59
+ */
60
+ export class X402Client {
61
+ #signer;
62
+ #address;
63
+ #maxAutoPayUsdc;
64
+ /** The last PAYMENT-REQUIRED decoded before payment. Useful for logging/display. */
65
+ lastPayment = null;
66
+ constructor({ signer, address, maxAutoPayUsdc = 0.1 }) {
67
+ this.#signer = signer;
68
+ this.#address = address;
69
+ this.#maxAutoPayUsdc = maxAutoPayUsdc;
70
+ }
71
+ /** Make a fetch request, auto-paying any 402 responses within the configured limit. */
72
+ async fetch(url, init) {
73
+ const response = await globalThis.fetch(url, init);
74
+ if (response.status === 402) {
75
+ return this.#handle402(url, response, init);
76
+ }
77
+ return response;
78
+ }
79
+ async #handle402(url, response, init) {
80
+ // 1. Decode PAYMENT-REQUIRED header (header names are case-insensitive per HTTP spec).
81
+ const raw = response.headers.get("payment-required");
82
+ if (!raw) {
83
+ throw new Error("402 response missing PAYMENT-REQUIRED header");
84
+ }
85
+ const required = JSON.parse(Buffer.from(raw, "base64").toString("utf8"));
86
+ // 2. Only the "exact" scheme is supported in V5.
87
+ if (required.scheme !== "exact") {
88
+ throw new Error(`Unsupported x402 scheme: ${required.scheme}`);
89
+ }
90
+ // Store for caller inspection (V2 fields: resource, description, mimeType).
91
+ this.lastPayment = required;
92
+ // 3. Check auto-pay limit.
93
+ const amountBaseUnits = BigInt(required.amount);
94
+ const amountUsdc = Number(amountBaseUnits) / 1_000_000;
95
+ if (amountUsdc > this.#maxAutoPayUsdc) {
96
+ throw new AllowanceExceededError(amountUsdc, this.#maxAutoPayUsdc);
97
+ }
98
+ // 4. Parse chainId from CAIP-2 network string (e.g. "eip155:84532" → 84532).
99
+ const chainId = parseInt(required.network.split(":")[1], 10);
100
+ // 5. Build EIP-3009 authorization fields.
101
+ const nowSecs = Math.floor(Date.now() / 1000);
102
+ const validBefore = nowSecs + (required.maxTimeoutSeconds ?? 60);
103
+ const nonce = `0x${randomBytes(32).toString("hex")}`;
104
+ const domain = {
105
+ name: "USD Coin",
106
+ version: "2",
107
+ chainId,
108
+ verifyingContract: required.asset,
109
+ };
110
+ // viem requires uint256 values as bigint.
111
+ const message = {
112
+ from: this.#address,
113
+ to: required.payTo,
114
+ value: amountBaseUnits,
115
+ validAfter: BigInt(0),
116
+ validBefore: BigInt(validBefore),
117
+ nonce,
118
+ };
119
+ // 6. Sign EIP-712 typed data.
120
+ const signature = await this.#signer.signTypedData(domain,
121
+ // Cast: EIP3009_TYPES satisfies TypedDataTypes but has readonly arrays.
122
+ EIP3009_TYPES, message);
123
+ // 7. Build PAYMENT-SIGNATURE JSON payload.
124
+ const paymentPayload = {
125
+ scheme: required.scheme,
126
+ network: required.network,
127
+ x402Version: 1,
128
+ payload: {
129
+ signature,
130
+ authorization: {
131
+ from: this.#address,
132
+ to: required.payTo,
133
+ value: required.amount, // string (base units)
134
+ validAfter: "0",
135
+ validBefore: String(validBefore),
136
+ nonce,
137
+ },
138
+ },
139
+ };
140
+ const paymentHeader = Buffer.from(JSON.stringify(paymentPayload)).toString("base64");
141
+ // 8. Retry with PAYMENT-SIGNATURE header.
142
+ const newHeaders = new Headers(init?.headers);
143
+ newHeaders.set("PAYMENT-SIGNATURE", paymentHeader);
144
+ return globalThis.fetch(url, { ...init, headers: newHeaders });
145
+ }
146
+ /** Prevent address leakage via structured cloning / serialisation. */
147
+ toJSON() {
148
+ return { address: this.#address };
149
+ }
150
+ }
151
+ //# sourceMappingURL=x402.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"x402.js","sourceRoot":"","sources":["../src/x402.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,27 @@
1
+ import js from "@eslint/js";
2
+ import tsPlugin from "@typescript-eslint/eslint-plugin";
3
+ import tsParser from "@typescript-eslint/parser";
4
+ import globals from "globals";
5
+
6
+ export default [
7
+ js.configs.recommended,
8
+ {
9
+ files: ["src/**/*.ts"],
10
+ plugins: { "@typescript-eslint": tsPlugin },
11
+ languageOptions: {
12
+ parser: tsParser,
13
+ parserOptions: { project: "./tsconfig.json" },
14
+ globals: {
15
+ ...globals.node,
16
+ fetch: "readonly",
17
+ },
18
+ },
19
+ rules: {
20
+ ...tsPlugin.configs.recommended.rules,
21
+ "@typescript-eslint/no-explicit-any": "error",
22
+ "@typescript-eslint/explicit-function-return-type": "off",
23
+ "@typescript-eslint/no-unused-vars": ["error", { argsIgnorePattern: "^_" }],
24
+ "no-undef": "off", // TypeScript handles this via strict type checking
25
+ },
26
+ },
27
+ ];
package/package.json ADDED
@@ -0,0 +1,39 @@
1
+ {
2
+ "name": "@remitmd/sdk",
3
+ "version": "0.1.0",
4
+ "description": "TypeScript SDK for the remit.md universal AI payment protocol",
5
+ "type": "module",
6
+ "main": "./dist/index.js",
7
+ "types": "./dist/index.d.ts",
8
+ "exports": {
9
+ ".": {
10
+ "import": "./dist/index.js",
11
+ "types": "./dist/index.d.ts"
12
+ }
13
+ },
14
+ "scripts": {
15
+ "build": "tsc",
16
+ "test": "node --import tsx --test tests/*.ts",
17
+ "compliance": "node --import tsx --test tests/compliance/*.ts",
18
+ "acceptance": "node --import tsx --test --test-concurrency=1 tests/acceptance/*.test.ts",
19
+ "typecheck": "tsc --noEmit",
20
+ "lint": "eslint src"
21
+ },
22
+ "dependencies": {
23
+ "viem": "^2.0.0"
24
+ },
25
+ "devDependencies": {
26
+ "@eslint/js": "^9.39.4",
27
+ "@types/node": "^20.0.0",
28
+ "@typescript-eslint/eslint-plugin": "^7.0.0",
29
+ "@typescript-eslint/parser": "^7.0.0",
30
+ "eslint": "^9.0.0",
31
+ "globals": "^17.4.0",
32
+ "tsx": "^4.0.0",
33
+ "typescript": "^5.0.0"
34
+ },
35
+ "engines": {
36
+ "node": ">=20.0.0"
37
+ },
38
+ "license": "MIT"
39
+ }
package/src/a2a.ts ADDED
@@ -0,0 +1,241 @@
1
+ /**
2
+ * A2A / AP2 — agent card discovery and A2A JSON-RPC task client.
3
+ *
4
+ * Spec: https://google.github.io/A2A/specification/
5
+ * AP2: https://ap2-protocol.org/
6
+ */
7
+
8
+ import type { Signer } from "./signer.js";
9
+ import { AuthenticatedClient } from "./http.js";
10
+ import { CHAIN_IDS } from "./client.js";
11
+
12
+ // ─── Agent Card types ─────────────────────────────────────────────────────────
13
+
14
+ export interface A2AExtension {
15
+ uri: string;
16
+ description: string;
17
+ required: boolean;
18
+ }
19
+
20
+ export interface A2ACapabilities {
21
+ streaming: boolean;
22
+ pushNotifications: boolean;
23
+ stateTransitionHistory: boolean;
24
+ extensions: A2AExtension[];
25
+ }
26
+
27
+ export interface A2ASkill {
28
+ id: string;
29
+ name: string;
30
+ description: string;
31
+ tags: string[];
32
+ }
33
+
34
+ export interface A2AFees {
35
+ standardBps: number;
36
+ preferredBps: number;
37
+ cliffUsd: number;
38
+ }
39
+
40
+ export interface A2AX402 {
41
+ settleEndpoint: string;
42
+ assets: Record<string, string>;
43
+ fees: A2AFees;
44
+ }
45
+
46
+ export interface AgentCard {
47
+ protocolVersion: string;
48
+ name: string;
49
+ description: string;
50
+ /** A2A JSON-RPC endpoint URL (POST). */
51
+ url: string;
52
+ version: string;
53
+ documentationUrl: string;
54
+ capabilities: A2ACapabilities;
55
+ authentication: unknown[];
56
+ skills: A2ASkill[];
57
+ x402: A2AX402;
58
+ }
59
+
60
+ /**
61
+ * Fetch and parse the A2A agent card from `baseUrl`/.well-known/agent-card.json.
62
+ *
63
+ * @example
64
+ * ```ts
65
+ * const card = await discoverAgent("https://remit.md");
66
+ * console.log(card.name, card.url);
67
+ * ```
68
+ */
69
+ export async function discoverAgent(baseUrl: string): Promise<AgentCard> {
70
+ const url = baseUrl.replace(/\/$/, "") + "/.well-known/agent-card.json";
71
+ const res = await fetch(url, { headers: { Accept: "application/json" } });
72
+ if (!res.ok) {
73
+ throw new Error(`Agent card discovery failed: HTTP ${res.status} ${res.statusText}`);
74
+ }
75
+ return (await res.json()) as AgentCard;
76
+ }
77
+
78
+ // ─── A2A task types ───────────────────────────────────────────────────────────
79
+
80
+ export interface A2ATaskStatus {
81
+ state: "completed" | "failed" | "canceled" | "working" | string;
82
+ message?: { text?: string };
83
+ }
84
+
85
+ export interface A2AArtifactPart {
86
+ kind: string;
87
+ data?: Record<string, unknown>;
88
+ }
89
+
90
+ export interface A2AArtifact {
91
+ name?: string;
92
+ parts: A2AArtifactPart[];
93
+ }
94
+
95
+ export interface A2ATask {
96
+ id: string;
97
+ status: A2ATaskStatus;
98
+ artifacts: A2AArtifact[];
99
+ }
100
+
101
+ /** Extract `txHash` from task artifacts, if present. */
102
+ export function getTaskTxHash(task: A2ATask): string | undefined {
103
+ for (const artifact of task.artifacts) {
104
+ for (const part of artifact.parts) {
105
+ const tx = part.data?.["txHash"];
106
+ if (typeof tx === "string") return tx;
107
+ }
108
+ }
109
+ return undefined;
110
+ }
111
+
112
+ // ─── IntentMandate ────────────────────────────────────────────────────────────
113
+
114
+ export interface IntentMandate {
115
+ mandateId: string;
116
+ expiresAt: string;
117
+ issuer: string;
118
+ allowance: {
119
+ maxAmount: string;
120
+ currency: string;
121
+ };
122
+ }
123
+
124
+ // ─── A2A client options ───────────────────────────────────────────────────────
125
+
126
+ export interface A2AClientOptions {
127
+ /** Full A2A endpoint URL from the agent card (e.g. ``"https://remit.md/a2a"``). */
128
+ endpoint: string;
129
+ signer: Signer;
130
+ chainId: number;
131
+ verifyingContract?: string;
132
+ }
133
+
134
+ export interface SendOptions {
135
+ to: string;
136
+ amount: number;
137
+ memo?: string;
138
+ mandate?: IntentMandate;
139
+ }
140
+
141
+ // ─── A2A client ───────────────────────────────────────────────────────────────
142
+
143
+ /**
144
+ * A2A JSON-RPC client — send payments and manage tasks via the A2A protocol.
145
+ *
146
+ * @example
147
+ * ```ts
148
+ * import { discoverAgent, A2AClient } from "@remitmd/sdk";
149
+ * import { PrivateKeySigner } from "@remitmd/sdk";
150
+ *
151
+ * const card = await discoverAgent("https://remit.md");
152
+ * const signer = new PrivateKeySigner(process.env.REMITMD_KEY!);
153
+ * const client = A2AClient.fromCard(card, signer);
154
+ * const task = await client.send({ to: "0xRecipient...", amount: 10 });
155
+ * console.log(task.status.state, getTaskTxHash(task));
156
+ * ```
157
+ */
158
+ export class A2AClient {
159
+ private readonly _http: AuthenticatedClient;
160
+ private readonly _path: string;
161
+
162
+ constructor(opts: A2AClientOptions) {
163
+ const { endpoint, signer, chainId, verifyingContract = "" } = opts;
164
+ const parsed = new URL(endpoint);
165
+ const baseUrl = `${parsed.protocol}//${parsed.host}`;
166
+ this._path = parsed.pathname || "/a2a";
167
+ this._http = new AuthenticatedClient({ signer, baseUrl, chainId, verifyingContract });
168
+ }
169
+
170
+ /** Convenience constructor from an :class:`AgentCard` and a signer. */
171
+ static fromCard(
172
+ card: AgentCard,
173
+ signer: Signer,
174
+ opts?: { chain?: string; verifyingContract?: string },
175
+ ): A2AClient {
176
+ const chain = opts?.chain ?? "base";
177
+ const chainId = CHAIN_IDS[chain] ?? CHAIN_IDS["base"]!;
178
+ return new A2AClient({
179
+ endpoint: card.url,
180
+ signer,
181
+ chainId,
182
+ verifyingContract: opts?.verifyingContract ?? "",
183
+ });
184
+ }
185
+
186
+ /**
187
+ * Send a direct USDC payment via ``message/send``.
188
+ *
189
+ * @returns :interface:`A2ATask` with ``status.state === "completed"`` on success.
190
+ */
191
+ async send(opts: SendOptions): Promise<A2ATask> {
192
+ const { to, amount, memo = "", mandate } = opts;
193
+ const nonce = crypto.randomUUID().replace(/-/g, "");
194
+ const messageId = crypto.randomUUID().replace(/-/g, "");
195
+
196
+ const message: Record<string, unknown> = {
197
+ messageId,
198
+ role: "user",
199
+ parts: [
200
+ {
201
+ kind: "data",
202
+ data: {
203
+ model: "direct",
204
+ to,
205
+ amount: amount.toFixed(2),
206
+ memo,
207
+ nonce,
208
+ },
209
+ },
210
+ ],
211
+ };
212
+
213
+ if (mandate) {
214
+ message["metadata"] = { mandate };
215
+ }
216
+
217
+ return this._rpc<A2ATask>("message/send", { message }, messageId);
218
+ }
219
+
220
+ /** Fetch the current state of an A2A task by ID. */
221
+ async getTask(taskId: string): Promise<A2ATask> {
222
+ return this._rpc<A2ATask>("tasks/get", { id: taskId }, taskId.slice(0, 16));
223
+ }
224
+
225
+ /** Cancel an in-progress A2A task. */
226
+ async cancelTask(taskId: string): Promise<A2ATask> {
227
+ return this._rpc<A2ATask>("tasks/cancel", { id: taskId }, taskId.slice(0, 16));
228
+ }
229
+
230
+ private async _rpc<T>(method: string, params: unknown, callId: string): Promise<T> {
231
+ const body = { jsonrpc: "2.0", id: callId, method, params };
232
+ const data = await this._http.post<{ result?: T; error?: { message?: string } }>(
233
+ this._path,
234
+ body,
235
+ );
236
+ if (data.error) {
237
+ throw new Error(`A2A error: ${data.error.message ?? JSON.stringify(data.error)}`);
238
+ }
239
+ return (data.result ?? (data as unknown as T)) as T;
240
+ }
241
+ }
package/src/client.ts ADDED
@@ -0,0 +1,104 @@
1
+ /**
2
+ * RemitClient — read-only operations, no private key required.
3
+ */
4
+
5
+ import type { WalletStatus, Reputation, ContractAddresses } from "./models/index.js";
6
+ import type { Invoice } from "./models/invoice.js";
7
+ import type { Escrow } from "./models/escrow.js";
8
+ import type { Tab } from "./models/tab.js";
9
+ import type { Stream } from "./models/stream.js";
10
+ import type { Bounty } from "./models/bounty.js";
11
+ import type { Deposit } from "./models/deposit.js";
12
+ const DEFAULT_API_URLS: Record<string, string> = {
13
+ base: "https://api.remit.md/api/v0",
14
+ "base-sepolia": "https://testnet.remit.md/api/v0",
15
+ localhost: "http://localhost:3000/api/v0",
16
+ };
17
+
18
+ export const CHAIN_IDS: Record<string, number> = {
19
+ base: 8453,
20
+ "base-sepolia": 84532,
21
+ localhost: 31337,
22
+ };
23
+
24
+ export interface RemitClientOptions {
25
+ chain?: string;
26
+ testnet?: boolean;
27
+ apiUrl?: string;
28
+ }
29
+
30
+ export class RemitClient {
31
+ protected readonly _chain: string;
32
+ protected readonly _apiUrl: string;
33
+ protected readonly _chainId: number;
34
+ #contractsCache: ContractAddresses | null = null;
35
+
36
+ constructor(options: RemitClientOptions = {}) {
37
+ const { chain = "base", testnet = false, apiUrl } = options;
38
+ this._chain = testnet && !chain.includes("sepolia") ? `${chain}-sepolia` : chain;
39
+ this._apiUrl =
40
+ apiUrl ?? DEFAULT_API_URLS[this._chain] ?? DEFAULT_API_URLS["base"]!;
41
+ this._chainId = CHAIN_IDS[this._chain] ?? CHAIN_IDS["base"]!;
42
+ }
43
+
44
+ protected async _fetch<T>(path: string): Promise<T> {
45
+ const response = await fetch(`${this._apiUrl}${path}`, {
46
+ headers: { "Content-Type": "application/json" },
47
+ });
48
+ if (!response.ok) {
49
+ throw new Error(`HTTP ${response.status}: ${response.statusText}`);
50
+ }
51
+ return (await response.json()) as T;
52
+ }
53
+
54
+ getInvoice(invoiceId: string): Promise<Invoice> {
55
+ return this._fetch<Invoice>(`/invoices/${invoiceId}`);
56
+ }
57
+
58
+ getEscrow(invoiceId: string): Promise<Escrow> {
59
+ return this._fetch<Escrow>(`/escrows/${invoiceId}`);
60
+ }
61
+
62
+ getTab(tabId: string): Promise<Tab> {
63
+ return this._fetch<Tab>(`/tabs/${tabId}`);
64
+ }
65
+
66
+ getStream(streamId: string): Promise<Stream> {
67
+ return this._fetch<Stream>(`/streams/${streamId}`);
68
+ }
69
+
70
+ getBounty(bountyId: string): Promise<Bounty> {
71
+ return this._fetch<Bounty>(`/bounties/${bountyId}`);
72
+ }
73
+
74
+ getDeposit(depositId: string): Promise<Deposit> {
75
+ return this._fetch<Deposit>(`/deposits/${depositId}`);
76
+ }
77
+
78
+ getStatus(wallet: string): Promise<WalletStatus> {
79
+ return this._fetch<WalletStatus>(`/status/${wallet}`);
80
+ }
81
+
82
+ getReputation(wallet: string): Promise<Reputation> {
83
+ return this._fetch<Reputation>(`/reputation/${wallet}`);
84
+ }
85
+
86
+ /** Get deployed contract addresses. Cached for the lifetime of this client instance. */
87
+ async getContracts(): Promise<ContractAddresses> {
88
+ if (this.#contractsCache) return this.#contractsCache;
89
+ const contracts = await this._fetch<ContractAddresses>("/contracts");
90
+ this.#contractsCache = contracts;
91
+ return contracts;
92
+ }
93
+
94
+ listBounties(options: { status?: string; limit?: number; poster?: string; submitter?: string } = {}): Promise<Bounty[]> {
95
+ const params = new URLSearchParams();
96
+ if (options.status) params.set("status", options.status);
97
+ if (options.limit) params.set("limit", String(options.limit));
98
+ if (options.poster) params.set("poster", options.poster);
99
+ if (options.submitter) params.set("submitter", options.submitter);
100
+ const qs = params.toString();
101
+ return this._fetch<Bounty[]>(`/bounties${qs ? `?${qs}` : ""}`);
102
+ }
103
+
104
+ }