@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.
- package/LICENSE +21 -0
- package/README.md +250 -0
- package/dist/a2a.d.ts +137 -0
- package/dist/a2a.d.ts.map +1 -0
- package/dist/a2a.js +121 -0
- package/dist/a2a.js.map +1 -0
- package/dist/client.d.ts +41 -0
- package/dist/client.d.ts.map +1 -0
- package/dist/client.js +81 -0
- package/dist/client.js.map +1 -0
- package/dist/errors.d.ts +108 -0
- package/dist/errors.d.ts.map +1 -0
- package/dist/errors.js +218 -0
- package/dist/errors.js.map +1 -0
- package/dist/http.d.ts +23 -0
- package/dist/http.d.ts.map +1 -0
- package/dist/http.js +150 -0
- package/dist/http.js.map +1 -0
- package/dist/index.d.ts +18 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +21 -0
- package/dist/index.js.map +1 -0
- package/dist/integrations/vercel-ai.d.ts +44 -0
- package/dist/integrations/vercel-ai.d.ts.map +1 -0
- package/dist/integrations/vercel-ai.js +175 -0
- package/dist/integrations/vercel-ai.js.map +1 -0
- package/dist/models/bounty.d.ts +22 -0
- package/dist/models/bounty.d.ts.map +1 -0
- package/dist/models/bounty.js +2 -0
- package/dist/models/bounty.js.map +1 -0
- package/dist/models/common.d.ts +78 -0
- package/dist/models/common.d.ts.map +1 -0
- package/dist/models/common.js +3 -0
- package/dist/models/common.js.map +1 -0
- package/dist/models/deposit.d.ts +13 -0
- package/dist/models/deposit.d.ts.map +1 -0
- package/dist/models/deposit.js +2 -0
- package/dist/models/deposit.js.map +1 -0
- package/dist/models/escrow.d.ts +16 -0
- package/dist/models/escrow.d.ts.map +1 -0
- package/dist/models/escrow.js +2 -0
- package/dist/models/escrow.js.map +1 -0
- package/dist/models/index.d.ts +9 -0
- package/dist/models/index.d.ts.map +1 -0
- package/dist/models/index.js +9 -0
- package/dist/models/index.js.map +1 -0
- package/dist/models/invoice.d.ts +30 -0
- package/dist/models/invoice.d.ts.map +1 -0
- package/dist/models/invoice.js +2 -0
- package/dist/models/invoice.js.map +1 -0
- package/dist/models/reputation.d.ts +7 -0
- package/dist/models/reputation.d.ts.map +1 -0
- package/dist/models/reputation.js +2 -0
- package/dist/models/reputation.js.map +1 -0
- package/dist/models/stream.d.ts +15 -0
- package/dist/models/stream.d.ts.map +1 -0
- package/dist/models/stream.js +2 -0
- package/dist/models/stream.js.map +1 -0
- package/dist/models/tab.d.ts +21 -0
- package/dist/models/tab.d.ts.map +1 -0
- package/dist/models/tab.js +2 -0
- package/dist/models/tab.js.map +1 -0
- package/dist/provider.d.ts +135 -0
- package/dist/provider.d.ts.map +1 -0
- package/dist/provider.js +218 -0
- package/dist/provider.js.map +1 -0
- package/dist/signer.d.ts +31 -0
- package/dist/signer.d.ts.map +1 -0
- package/dist/signer.js +35 -0
- package/dist/signer.js.map +1 -0
- package/dist/testing/local.d.ts +31 -0
- package/dist/testing/local.d.ts.map +1 -0
- package/dist/testing/local.js +100 -0
- package/dist/testing/local.js.map +1 -0
- package/dist/testing/mock.d.ts +95 -0
- package/dist/testing/mock.d.ts.map +1 -0
- package/dist/testing/mock.js +407 -0
- package/dist/testing/mock.js.map +1 -0
- package/dist/wallet.d.ts +162 -0
- package/dist/wallet.d.ts.map +1 -0
- package/dist/wallet.js +365 -0
- package/dist/wallet.js.map +1 -0
- package/dist/x402.d.ts +78 -0
- package/dist/x402.d.ts.map +1 -0
- package/dist/x402.js +151 -0
- package/dist/x402.js.map +1 -0
- package/eslint.config.js +27 -0
- package/package.json +39 -0
- package/src/a2a.ts +241 -0
- package/src/client.ts +104 -0
- package/src/errors.ts +261 -0
- package/src/http.ts +190 -0
- package/src/index.ts +94 -0
- package/src/integrations/vercel-ai.ts +213 -0
- package/src/models/bounty.ts +23 -0
- package/src/models/common.ts +106 -0
- package/src/models/deposit.ts +13 -0
- package/src/models/escrow.ts +16 -0
- package/src/models/index.ts +8 -0
- package/src/models/invoice.ts +32 -0
- package/src/models/reputation.ts +7 -0
- package/src/models/stream.ts +15 -0
- package/src/models/tab.ts +22 -0
- package/src/provider.ts +281 -0
- package/src/signer.ts +70 -0
- package/src/testing/local.ts +118 -0
- package/src/testing/mock.ts +507 -0
- package/src/wallet.ts +546 -0
- package/src/x402.ts +202 -0
- package/tests/acceptance/bounty.test.ts +82 -0
- package/tests/acceptance/deposit.test.ts +70 -0
- package/tests/acceptance/direct.test.ts +53 -0
- package/tests/acceptance/escrow.test.ts +67 -0
- package/tests/acceptance/setup.ts +113 -0
- package/tests/acceptance/stream.test.ts +98 -0
- package/tests/acceptance/tab.test.ts +108 -0
- package/tests/acceptance/x402.test.ts +140 -0
- package/tests/compliance/auth.ts +69 -0
- package/tests/compliance/escrows.ts +96 -0
- package/tests/compliance/helpers.ts +90 -0
- package/tests/compliance/payments.ts +69 -0
- package/tests/compliance/tabs.ts +52 -0
- package/tests/test_a2a.ts +151 -0
- package/tests/test_errors.ts +80 -0
- package/tests/test_golden_vectors.ts +162 -0
- package/tests/test_integrations.ts +115 -0
- package/tests/test_mock.ts +217 -0
- package/tests/test_permit.ts +216 -0
- package/tests/test_provider.ts +304 -0
- package/tests/test_wallet.ts +108 -0
- package/tests/test_x402.ts +302 -0
- package/tsconfig.json +19 -0
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
import { describe, it, mock, beforeEach, afterEach } from "node:test";
|
|
2
|
+
import assert from "node:assert/strict";
|
|
3
|
+
|
|
4
|
+
import { discoverAgent, getTaskTxHash, A2AClient } from "../src/a2a.js";
|
|
5
|
+
import type { AgentCard, A2ATask } from "../src/a2a.js";
|
|
6
|
+
import { PrivateKeySigner } from "../src/signer.js";
|
|
7
|
+
|
|
8
|
+
// ─── Fixtures ─────────────────────────────────────────────────────────────────
|
|
9
|
+
|
|
10
|
+
const CARD_DATA: AgentCard = {
|
|
11
|
+
protocolVersion: "0.6",
|
|
12
|
+
name: "remit.md",
|
|
13
|
+
description: "USDC payment protocol for AI agents.",
|
|
14
|
+
url: "https://remit.md/a2a",
|
|
15
|
+
version: "0.1.0",
|
|
16
|
+
documentationUrl: "https://remit.md/docs",
|
|
17
|
+
capabilities: {
|
|
18
|
+
streaming: false,
|
|
19
|
+
pushNotifications: false,
|
|
20
|
+
stateTransitionHistory: true,
|
|
21
|
+
extensions: [
|
|
22
|
+
{
|
|
23
|
+
uri: "https://ap2-protocol.org/ext/payment-processor",
|
|
24
|
+
description: "AP2 payment processor",
|
|
25
|
+
required: false,
|
|
26
|
+
},
|
|
27
|
+
],
|
|
28
|
+
},
|
|
29
|
+
authentication: [],
|
|
30
|
+
skills: [
|
|
31
|
+
{
|
|
32
|
+
id: "direct-payment",
|
|
33
|
+
name: "Direct Payment",
|
|
34
|
+
description: "Send USDC directly.",
|
|
35
|
+
tags: ["payment", "usdc"],
|
|
36
|
+
},
|
|
37
|
+
{
|
|
38
|
+
id: "x402-paywall",
|
|
39
|
+
name: "x402 Paywall",
|
|
40
|
+
description: "HTTP 402 payment rail.",
|
|
41
|
+
tags: ["x402", "paywall"],
|
|
42
|
+
},
|
|
43
|
+
],
|
|
44
|
+
x402: {
|
|
45
|
+
settleEndpoint: "https://remit.md/api/v0/x402/settle",
|
|
46
|
+
assets: { "eip155:8453": "0xUSDC" },
|
|
47
|
+
fees: { standardBps: 100, preferredBps: 50, cliffUsd: 10000 },
|
|
48
|
+
},
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
// ─── discoverAgent ─────────────────────────────────────────────────────────────
|
|
52
|
+
|
|
53
|
+
describe("discoverAgent", () => {
|
|
54
|
+
let fetchMock: ReturnType<typeof mock.fn>;
|
|
55
|
+
|
|
56
|
+
beforeEach(() => {
|
|
57
|
+
fetchMock = mock.fn(async (_url: string) => ({
|
|
58
|
+
ok: true,
|
|
59
|
+
status: 200,
|
|
60
|
+
json: async () => CARD_DATA,
|
|
61
|
+
}));
|
|
62
|
+
(globalThis as unknown as { fetch: unknown }).fetch = fetchMock;
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
afterEach(() => {
|
|
66
|
+
mock.restoreAll();
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
it("fetches from /.well-known/agent-card.json", async () => {
|
|
70
|
+
const card = await discoverAgent("https://remit.md");
|
|
71
|
+
assert.equal(fetchMock.mock.calls.length, 1);
|
|
72
|
+
const url = fetchMock.mock.calls[0]!.arguments[0] as string;
|
|
73
|
+
assert.equal(url, "https://remit.md/.well-known/agent-card.json");
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
it("strips trailing slash from base URL", async () => {
|
|
77
|
+
await discoverAgent("https://remit.md/");
|
|
78
|
+
const url = fetchMock.mock.calls[0]!.arguments[0] as string;
|
|
79
|
+
assert.equal(url, "https://remit.md/.well-known/agent-card.json");
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
it("returns parsed agent card", async () => {
|
|
83
|
+
const card = await discoverAgent("https://remit.md");
|
|
84
|
+
assert.equal(card.name, "remit.md");
|
|
85
|
+
assert.equal(card.url, "https://remit.md/a2a");
|
|
86
|
+
assert.equal(card.protocolVersion, "0.6");
|
|
87
|
+
assert.equal(card.skills.length, 2);
|
|
88
|
+
assert.equal(card.x402.fees.standardBps, 100);
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
it("throws on non-200 response", async () => {
|
|
92
|
+
fetchMock = mock.fn(async () => ({ ok: false, status: 404, statusText: "Not Found" }));
|
|
93
|
+
(globalThis as unknown as { fetch: unknown }).fetch = fetchMock;
|
|
94
|
+
await assert.rejects(
|
|
95
|
+
() => discoverAgent("https://notfound.example"),
|
|
96
|
+
/404/,
|
|
97
|
+
);
|
|
98
|
+
});
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
// ─── getTaskTxHash ─────────────────────────────────────────────────────────────
|
|
102
|
+
|
|
103
|
+
describe("getTaskTxHash", () => {
|
|
104
|
+
it("extracts txHash from artifacts", () => {
|
|
105
|
+
const task: A2ATask = {
|
|
106
|
+
id: "task_1",
|
|
107
|
+
status: { state: "completed" },
|
|
108
|
+
artifacts: [
|
|
109
|
+
{ parts: [{ kind: "data", data: { txHash: "0xdeadbeef" } }] },
|
|
110
|
+
],
|
|
111
|
+
};
|
|
112
|
+
assert.equal(getTaskTxHash(task), "0xdeadbeef");
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
it("returns undefined when no artifacts", () => {
|
|
116
|
+
const task: A2ATask = {
|
|
117
|
+
id: "task_2",
|
|
118
|
+
status: { state: "completed" },
|
|
119
|
+
artifacts: [],
|
|
120
|
+
};
|
|
121
|
+
assert.equal(getTaskTxHash(task), undefined);
|
|
122
|
+
});
|
|
123
|
+
|
|
124
|
+
it("returns undefined when artifacts have no txHash", () => {
|
|
125
|
+
const task: A2ATask = {
|
|
126
|
+
id: "task_3",
|
|
127
|
+
status: { state: "failed" },
|
|
128
|
+
artifacts: [{ parts: [{ kind: "data", data: { foo: "bar" } }] }],
|
|
129
|
+
};
|
|
130
|
+
assert.equal(getTaskTxHash(task), undefined);
|
|
131
|
+
});
|
|
132
|
+
});
|
|
133
|
+
|
|
134
|
+
// ─── A2AClient constructor ────────────────────────────────────────────────────
|
|
135
|
+
|
|
136
|
+
describe("A2AClient", () => {
|
|
137
|
+
const TEST_KEY = "0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80";
|
|
138
|
+
|
|
139
|
+
it("fromCard creates client with correct path", () => {
|
|
140
|
+
const signer = new PrivateKeySigner(TEST_KEY);
|
|
141
|
+
const client = A2AClient.fromCard(CARD_DATA, signer);
|
|
142
|
+
// Access internal path (private field — test via behavior or cast)
|
|
143
|
+
assert.ok(client instanceof A2AClient);
|
|
144
|
+
});
|
|
145
|
+
|
|
146
|
+
it("fromCard accepts chain option", () => {
|
|
147
|
+
const signer = new PrivateKeySigner(TEST_KEY);
|
|
148
|
+
const client = A2AClient.fromCard(CARD_DATA, signer, { chain: "base-sepolia" });
|
|
149
|
+
assert.ok(client instanceof A2AClient);
|
|
150
|
+
});
|
|
151
|
+
});
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
import { describe, it } from "node:test";
|
|
2
|
+
import assert from "node:assert/strict";
|
|
3
|
+
|
|
4
|
+
import {
|
|
5
|
+
fromErrorCode,
|
|
6
|
+
RemitError,
|
|
7
|
+
InsufficientBalanceError,
|
|
8
|
+
EscrowNotFoundError,
|
|
9
|
+
InvalidSignatureError,
|
|
10
|
+
TabNotFoundError,
|
|
11
|
+
StreamNotFoundError,
|
|
12
|
+
BountyNotFoundError,
|
|
13
|
+
RateLimitedError,
|
|
14
|
+
ChainMismatchError,
|
|
15
|
+
} from "../src/errors.js";
|
|
16
|
+
|
|
17
|
+
describe("Error classes", () => {
|
|
18
|
+
it("RemitError base has code and httpStatus", () => {
|
|
19
|
+
const e = new RemitError("test", "MY_CODE", 418);
|
|
20
|
+
assert.equal(e.code, "MY_CODE");
|
|
21
|
+
assert.equal(e.httpStatus, 418);
|
|
22
|
+
assert.equal(e.message, "test");
|
|
23
|
+
assert(e instanceof Error);
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
it("typed subclasses have correct codes", () => {
|
|
27
|
+
assert.equal(new InsufficientBalanceError().code, "INSUFFICIENT_BALANCE");
|
|
28
|
+
assert.equal(new EscrowNotFoundError().code, "ESCROW_NOT_FOUND");
|
|
29
|
+
assert.equal(new InvalidSignatureError().code, "INVALID_SIGNATURE");
|
|
30
|
+
assert.equal(new TabNotFoundError().code, "TAB_NOT_FOUND");
|
|
31
|
+
assert.equal(new StreamNotFoundError().code, "STREAM_NOT_FOUND");
|
|
32
|
+
assert.equal(new BountyNotFoundError().code, "BOUNTY_NOT_FOUND");
|
|
33
|
+
assert.equal(new RateLimitedError().code, "RATE_LIMITED");
|
|
34
|
+
assert.equal(new ChainMismatchError().code, "CHAIN_MISMATCH");
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
it("typed subclasses are instanceof RemitError", () => {
|
|
38
|
+
assert(new InsufficientBalanceError() instanceof RemitError);
|
|
39
|
+
assert(new EscrowNotFoundError() instanceof RemitError);
|
|
40
|
+
assert(new RateLimitedError() instanceof RemitError);
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
it("fromErrorCode returns correct subclass", () => {
|
|
44
|
+
const e = fromErrorCode("INSUFFICIENT_BALANCE", "custom message");
|
|
45
|
+
assert(e instanceof InsufficientBalanceError);
|
|
46
|
+
assert.equal(e.message, "custom message");
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
it("fromErrorCode with unknown code returns base RemitError", () => {
|
|
50
|
+
const e = fromErrorCode("TOTALLY_UNKNOWN_CODE");
|
|
51
|
+
assert(e instanceof RemitError);
|
|
52
|
+
assert.equal(e.code, "TOTALLY_UNKNOWN_CODE");
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
it("fromErrorCode uses default message when none provided", () => {
|
|
56
|
+
const e = fromErrorCode("ESCROW_NOT_FOUND");
|
|
57
|
+
assert(e.message.length > 0);
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
it("error name equals class name", () => {
|
|
61
|
+
const e = new InsufficientBalanceError();
|
|
62
|
+
assert.equal(e.name, "InsufficientBalanceError");
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
it("errors all have positive httpStatus", () => {
|
|
66
|
+
const codes = [
|
|
67
|
+
"INSUFFICIENT_BALANCE",
|
|
68
|
+
"ESCROW_NOT_FOUND",
|
|
69
|
+
"TAB_NOT_FOUND",
|
|
70
|
+
"RATE_LIMITED",
|
|
71
|
+
"CHAIN_MISMATCH",
|
|
72
|
+
"DISPUTE_ALREADY_FILED",
|
|
73
|
+
"VERSION_MISMATCH",
|
|
74
|
+
];
|
|
75
|
+
for (const code of codes) {
|
|
76
|
+
const e = fromErrorCode(code);
|
|
77
|
+
assert(e.httpStatus >= 400 && e.httpStatus < 600, `${code} has invalid httpStatus ${e.httpStatus}`);
|
|
78
|
+
}
|
|
79
|
+
});
|
|
80
|
+
});
|
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Golden vector tests: TypeScript SDK must reproduce the server's EIP-712 hashes exactly.
|
|
3
|
+
*
|
|
4
|
+
* Loads test-vectors/eip712.json (generated by `cargo run --bin gen_vectors` in remit-server)
|
|
5
|
+
* and verifies that viem's hashTypedData produces the same EIP-712 hash for each vector.
|
|
6
|
+
* Also verifies the ECDSA signature matches when signing with the same key.
|
|
7
|
+
*
|
|
8
|
+
* If any assertion fails, the TypeScript SDK's EIP-712 implementation diverges from the
|
|
9
|
+
* server and authentication WILL fail in production.
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
import { describe, it } from "node:test";
|
|
13
|
+
import assert from "node:assert/strict";
|
|
14
|
+
import { readFileSync } from "node:fs";
|
|
15
|
+
import { join, dirname } from "node:path";
|
|
16
|
+
import { fileURLToPath } from "node:url";
|
|
17
|
+
import { hashTypedData } from "viem";
|
|
18
|
+
import { privateKeyToAccount } from "viem/accounts";
|
|
19
|
+
|
|
20
|
+
// ─── Test fixtures ────────────────────────────────────────────────────────────
|
|
21
|
+
|
|
22
|
+
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
23
|
+
const VECTORS_PATH = join(__dirname, "../../test-vectors/eip712.json");
|
|
24
|
+
|
|
25
|
+
// Anvil test wallet #0 — same key used by gen_vectors in remit-server.
|
|
26
|
+
const TEST_PRIVATE_KEY =
|
|
27
|
+
"0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80" as const;
|
|
28
|
+
|
|
29
|
+
// EIP-712 typed struct — must match server's auth.rs exactly.
|
|
30
|
+
const EIP712_TYPES = {
|
|
31
|
+
APIRequest: [
|
|
32
|
+
{ name: "method", type: "string" },
|
|
33
|
+
{ name: "path", type: "string" },
|
|
34
|
+
{ name: "timestamp", type: "uint256" },
|
|
35
|
+
{ name: "nonce", type: "bytes32" },
|
|
36
|
+
],
|
|
37
|
+
} as const;
|
|
38
|
+
|
|
39
|
+
interface VectorDomain {
|
|
40
|
+
name: string;
|
|
41
|
+
version: string;
|
|
42
|
+
chain_id: number;
|
|
43
|
+
verifying_contract: string;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
interface VectorMessage {
|
|
47
|
+
method: string;
|
|
48
|
+
path: string;
|
|
49
|
+
timestamp: number;
|
|
50
|
+
nonce: string;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
interface Vector {
|
|
54
|
+
description: string;
|
|
55
|
+
domain: VectorDomain;
|
|
56
|
+
message: VectorMessage;
|
|
57
|
+
signer: string;
|
|
58
|
+
expected_domain_separator: string;
|
|
59
|
+
expected_struct_hash: string;
|
|
60
|
+
expected_hash: string;
|
|
61
|
+
expected_signature: string;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
interface VectorFile {
|
|
65
|
+
version: string;
|
|
66
|
+
vectors: Vector[];
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
interface VectorWithBigint extends Vector {
|
|
70
|
+
/** Timestamp as BigInt — JSON.parse loses precision for u64::MAX in JS. */
|
|
71
|
+
timestampBigint: bigint;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
function loadVectors(): VectorWithBigint[] {
|
|
75
|
+
const raw = readFileSync(VECTORS_PATH, "utf8");
|
|
76
|
+
const file = JSON.parse(raw) as VectorFile;
|
|
77
|
+
assert.ok(file.vectors.length > 0, "vectors array must not be empty");
|
|
78
|
+
|
|
79
|
+
// Extract timestamps as exact BigInts from the raw JSON string to avoid
|
|
80
|
+
// IEEE 754 precision loss for very large values (e.g. u64::MAX).
|
|
81
|
+
const timestampMatches = [...raw.matchAll(/"timestamp":\s*(\d+)/g)];
|
|
82
|
+
assert.equal(
|
|
83
|
+
timestampMatches.length,
|
|
84
|
+
file.vectors.length,
|
|
85
|
+
"Each vector must have exactly one timestamp field",
|
|
86
|
+
);
|
|
87
|
+
|
|
88
|
+
return file.vectors.map((v, i) => ({
|
|
89
|
+
...v,
|
|
90
|
+
timestampBigint: BigInt(timestampMatches[i]![1]),
|
|
91
|
+
}));
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
const vectors: VectorWithBigint[] = loadVectors();
|
|
95
|
+
|
|
96
|
+
// ─── Tests ────────────────────────────────────────────────────────────────────
|
|
97
|
+
|
|
98
|
+
describe("Golden vector: EIP-712 hash matches server", () => {
|
|
99
|
+
for (const vector of vectors) {
|
|
100
|
+
it(vector.description, () => {
|
|
101
|
+
const domain = {
|
|
102
|
+
name: vector.domain.name,
|
|
103
|
+
version: vector.domain.version,
|
|
104
|
+
chainId: vector.domain.chain_id,
|
|
105
|
+
verifyingContract: vector.domain.verifying_contract as `0x${string}`,
|
|
106
|
+
};
|
|
107
|
+
const message = {
|
|
108
|
+
method: vector.message.method,
|
|
109
|
+
path: vector.message.path,
|
|
110
|
+
timestamp: vector.timestampBigint,
|
|
111
|
+
nonce: vector.message.nonce as `0x${string}`,
|
|
112
|
+
};
|
|
113
|
+
|
|
114
|
+
const gotHash = hashTypedData({
|
|
115
|
+
domain,
|
|
116
|
+
types: EIP712_TYPES,
|
|
117
|
+
primaryType: "APIRequest",
|
|
118
|
+
message,
|
|
119
|
+
});
|
|
120
|
+
|
|
121
|
+
assert.equal(
|
|
122
|
+
gotHash,
|
|
123
|
+
vector.expected_hash,
|
|
124
|
+
`EIP-712 hash mismatch for '${vector.description}'`,
|
|
125
|
+
);
|
|
126
|
+
});
|
|
127
|
+
}
|
|
128
|
+
});
|
|
129
|
+
|
|
130
|
+
describe("Golden vector: signature matches server", () => {
|
|
131
|
+
const account = privateKeyToAccount(TEST_PRIVATE_KEY);
|
|
132
|
+
|
|
133
|
+
for (const vector of vectors) {
|
|
134
|
+
it(vector.description, async () => {
|
|
135
|
+
const domain = {
|
|
136
|
+
name: vector.domain.name,
|
|
137
|
+
version: vector.domain.version,
|
|
138
|
+
chainId: vector.domain.chain_id,
|
|
139
|
+
verifyingContract: vector.domain.verifying_contract as `0x${string}`,
|
|
140
|
+
};
|
|
141
|
+
const message = {
|
|
142
|
+
method: vector.message.method,
|
|
143
|
+
path: vector.message.path,
|
|
144
|
+
timestamp: vector.timestampBigint,
|
|
145
|
+
nonce: vector.message.nonce as `0x${string}`,
|
|
146
|
+
};
|
|
147
|
+
|
|
148
|
+
const gotSig = await account.signTypedData({
|
|
149
|
+
domain,
|
|
150
|
+
types: EIP712_TYPES,
|
|
151
|
+
primaryType: "APIRequest",
|
|
152
|
+
message,
|
|
153
|
+
});
|
|
154
|
+
|
|
155
|
+
assert.equal(
|
|
156
|
+
gotSig,
|
|
157
|
+
vector.expected_signature,
|
|
158
|
+
`Signature mismatch for '${vector.description}'`,
|
|
159
|
+
);
|
|
160
|
+
});
|
|
161
|
+
}
|
|
162
|
+
});
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
import { describe, it, beforeEach } from "node:test";
|
|
2
|
+
import assert from "node:assert/strict";
|
|
3
|
+
|
|
4
|
+
import { MockRemit } from "../src/testing/mock.js";
|
|
5
|
+
import { remitTools } from "../src/integrations/vercel-ai.js";
|
|
6
|
+
|
|
7
|
+
describe("Vercel AI integration", () => {
|
|
8
|
+
let mock: MockRemit;
|
|
9
|
+
|
|
10
|
+
beforeEach(() => {
|
|
11
|
+
mock = new MockRemit();
|
|
12
|
+
});
|
|
13
|
+
|
|
14
|
+
it("remitTools returns all expected tool names", () => {
|
|
15
|
+
const payer = mock.createWallet(1000);
|
|
16
|
+
const tools = remitTools(payer);
|
|
17
|
+
const names = Object.keys(tools);
|
|
18
|
+
|
|
19
|
+
const expected = [
|
|
20
|
+
"remit_pay_direct",
|
|
21
|
+
"remit_check_balance",
|
|
22
|
+
"remit_get_status",
|
|
23
|
+
"remit_create_escrow",
|
|
24
|
+
"remit_release_escrow",
|
|
25
|
+
"remit_open_tab",
|
|
26
|
+
"remit_close_tab",
|
|
27
|
+
"remit_open_stream",
|
|
28
|
+
"remit_close_stream",
|
|
29
|
+
"remit_post_bounty",
|
|
30
|
+
"remit_place_deposit",
|
|
31
|
+
];
|
|
32
|
+
|
|
33
|
+
for (const name of expected) {
|
|
34
|
+
assert(names.includes(name), `Missing tool: ${name}`);
|
|
35
|
+
}
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
it("each tool has description and execute function", () => {
|
|
39
|
+
const payer = mock.createWallet(1000);
|
|
40
|
+
const tools = remitTools(payer);
|
|
41
|
+
|
|
42
|
+
for (const [name, tool] of Object.entries(tools)) {
|
|
43
|
+
assert(tool.description.length > 10, `${name} description too short`);
|
|
44
|
+
assert(typeof tool.execute === "function", `${name} execute is not a function`);
|
|
45
|
+
}
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
it("remit_check_balance returns balance", async () => {
|
|
49
|
+
const payer = mock.createWallet(750);
|
|
50
|
+
const tools = remitTools(payer);
|
|
51
|
+
const result = (await tools["remit_check_balance"]!.execute()) as { balance: number };
|
|
52
|
+
assert.equal(result.balance, 750);
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
it("remit_pay_direct transfers funds", async () => {
|
|
56
|
+
const payer = mock.createWallet(100);
|
|
57
|
+
const payee = mock.createWallet(0);
|
|
58
|
+
const tools = remitTools(payer);
|
|
59
|
+
|
|
60
|
+
await tools["remit_pay_direct"]!.execute(payee.address, 20, "test payment");
|
|
61
|
+
assert.equal(await payer.balance(), 80);
|
|
62
|
+
assert.equal(await payee.balance(), 20);
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
it("remit_create_escrow creates an escrow", async () => {
|
|
66
|
+
const payer = mock.createWallet(200);
|
|
67
|
+
const payee = mock.createWallet(0);
|
|
68
|
+
const tools = remitTools(payer);
|
|
69
|
+
|
|
70
|
+
const tx = (await tools["remit_create_escrow"]!.execute(
|
|
71
|
+
payee.address,
|
|
72
|
+
50,
|
|
73
|
+
"Build a widget",
|
|
74
|
+
)) as { status: string };
|
|
75
|
+
assert.equal(tx.status, "funded");
|
|
76
|
+
assert.equal(await payer.balance(), 150);
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
it("remit_open_tab opens a tab", async () => {
|
|
80
|
+
const payer = mock.createWallet(100);
|
|
81
|
+
const payee = mock.createWallet(0);
|
|
82
|
+
const tools = remitTools(payer);
|
|
83
|
+
|
|
84
|
+
const tab = (await tools["remit_open_tab"]!.execute(payee.address, 40, 2)) as {
|
|
85
|
+
status: string;
|
|
86
|
+
id: string;
|
|
87
|
+
};
|
|
88
|
+
assert.equal(tab.status, "open");
|
|
89
|
+
assert(tab.id.length > 0);
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
it("remit_open_stream opens a stream", async () => {
|
|
93
|
+
const payer = mock.createWallet(1000);
|
|
94
|
+
const payee = mock.createWallet(0);
|
|
95
|
+
const tools = remitTools(payer);
|
|
96
|
+
|
|
97
|
+
const stream = (await tools["remit_open_stream"]!.execute(payee.address, 0.1, 3600)) as {
|
|
98
|
+
status: string;
|
|
99
|
+
id: string;
|
|
100
|
+
};
|
|
101
|
+
assert.equal(stream.status, "active");
|
|
102
|
+
assert(stream.id.length > 0);
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
it("remit_post_bounty posts a bounty", async () => {
|
|
106
|
+
const poster = mock.createWallet(100);
|
|
107
|
+
const tools = remitTools(poster);
|
|
108
|
+
const deadline = Math.floor(Date.now() / 1000) + 86400;
|
|
109
|
+
|
|
110
|
+
const bounty = (await tools["remit_post_bounty"]!.execute(30, "Analyze this dataset", deadline)) as {
|
|
111
|
+
status: string;
|
|
112
|
+
};
|
|
113
|
+
assert.equal(bounty.status, "open");
|
|
114
|
+
});
|
|
115
|
+
});
|