@pay-skill/sdk 0.1.8 → 0.1.11

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 (58) hide show
  1. package/README.md +80 -91
  2. package/dist/auth.d.ts +11 -6
  3. package/dist/auth.d.ts.map +1 -1
  4. package/dist/auth.js +19 -7
  5. package/dist/auth.js.map +1 -1
  6. package/dist/errors.d.ts +4 -2
  7. package/dist/errors.d.ts.map +1 -1
  8. package/dist/errors.js +8 -3
  9. package/dist/errors.js.map +1 -1
  10. package/dist/index.d.ts +2 -13
  11. package/dist/index.d.ts.map +1 -1
  12. package/dist/index.js +1 -6
  13. package/dist/index.js.map +1 -1
  14. package/dist/keychain.d.ts +8 -0
  15. package/dist/keychain.d.ts.map +1 -0
  16. package/dist/keychain.js +17 -0
  17. package/dist/keychain.js.map +1 -0
  18. package/dist/wallet.d.ts +136 -104
  19. package/dist/wallet.d.ts.map +1 -1
  20. package/dist/wallet.js +658 -275
  21. package/dist/wallet.js.map +1 -1
  22. package/jsr.json +1 -1
  23. package/package.json +5 -2
  24. package/src/auth.ts +28 -18
  25. package/src/errors.ts +10 -3
  26. package/src/index.ts +12 -39
  27. package/src/keychain.ts +18 -0
  28. package/src/wallet.ts +1054 -355
  29. package/tests/test_auth_rejection.ts +43 -95
  30. package/tests/test_crypto.ts +59 -172
  31. package/tests/test_e2e.ts +46 -105
  32. package/tests/test_errors.ts +9 -1
  33. package/tests/test_ows.ts +153 -0
  34. package/tests/test_wallet.ts +194 -0
  35. package/dist/client.d.ts +0 -94
  36. package/dist/client.d.ts.map +0 -1
  37. package/dist/client.js +0 -443
  38. package/dist/client.js.map +0 -1
  39. package/dist/models.d.ts +0 -78
  40. package/dist/models.d.ts.map +0 -1
  41. package/dist/models.js +0 -2
  42. package/dist/models.js.map +0 -1
  43. package/dist/ows-signer.d.ts +0 -75
  44. package/dist/ows-signer.d.ts.map +0 -1
  45. package/dist/ows-signer.js +0 -130
  46. package/dist/ows-signer.js.map +0 -1
  47. package/dist/signer.d.ts +0 -46
  48. package/dist/signer.d.ts.map +0 -1
  49. package/dist/signer.js +0 -111
  50. package/dist/signer.js.map +0 -1
  51. package/src/client.ts +0 -644
  52. package/src/models.ts +0 -77
  53. package/src/ows-signer.ts +0 -223
  54. package/src/signer.ts +0 -147
  55. package/tests/test_ows_integration.ts +0 -92
  56. package/tests/test_ows_signer.ts +0 -365
  57. package/tests/test_signer.ts +0 -47
  58. package/tests/test_validation.ts +0 -66
package/tests/test_e2e.ts CHANGED
@@ -1,158 +1,99 @@
1
1
  /**
2
2
  * E2E acceptance tests — run against live testnet.
3
3
  *
4
- * Skip unless PAYSKILL_TESTNET_KEY is set. These hit the real testnet server
5
- * and exercise the full SDK → server round-trip with REAL authentication.
4
+ * Skip unless PAYSKILL_TESTNET_KEY is set.
6
5
  *
7
6
  * Usage:
8
- * PAYSKILL_TESTNET_KEY=0xdead... \
9
- * PAYSKILL_TESTNET_URL=http://204.168.133.111:3001/api/v1 \
10
- * node --import tsx --test tests/test_e2e.ts
7
+ * PAYSKILL_TESTNET_KEY=0xdead... node --import tsx --test tests/test_e2e.ts
11
8
  */
12
9
 
13
10
  import { describe, it, before } from "node:test";
14
11
  import assert from "node:assert/strict";
15
12
  import { randomUUID } from "node:crypto";
13
+ import { Wallet, PayValidationError } from "../src/index.js";
16
14
 
17
- import { PayClient, PayValidationError, PayServerError, buildAuthHeaders } from "../src/index.js";
18
- import type { WebhookRegistration, AuthHeaders } from "../src/index.js";
19
- import type { Hex } from "viem";
20
-
21
- const TESTNET_URL =
22
- process.env.PAYSKILL_TESTNET_URL ?? "http://204.168.133.111:3001/api/v1";
23
15
  const TESTNET_KEY = process.env.PAYSKILL_TESTNET_KEY ?? "";
24
-
25
- // Testnet contract addresses (Base Sepolia)
26
- const CHAIN_ID = 84532;
27
- const ROUTER_ADDRESS = "0xE0Aa45e6937F3b9Fc0BEe457361885Cb9bfC067F";
28
-
29
16
  const skip = !TESTNET_KEY;
30
17
 
31
- function makeClient(): PayClient {
32
- return new PayClient({
33
- apiUrl: TESTNET_URL,
34
- privateKey: TESTNET_KEY,
35
- chainId: CHAIN_ID,
36
- routerAddress: ROUTER_ADDRESS,
37
- });
18
+ function makeWallet(): Wallet {
19
+ return new Wallet({ privateKey: TESTNET_KEY, testnet: true });
38
20
  }
39
21
 
40
- // ── Auth Verification ──────────────────────────────────────────────
41
-
42
- describe("E2E: Auth works with real signing", { skip }, () => {
43
- let client: PayClient;
22
+ describe("E2E: Status + Balance", { skip }, () => {
23
+ let wallet: Wallet;
24
+ before(() => { wallet = makeWallet(); });
44
25
 
45
- before(() => {
46
- client = makeClient();
47
- });
48
-
49
- it("status endpoint returns valid response with real auth", async () => {
50
- const status = await client.getStatus();
51
- assert.ok(typeof status.address === "string");
26
+ it("status returns valid response", async () => {
27
+ const status = await wallet.status();
52
28
  assert.ok(status.address.startsWith("0x"));
53
- assert.ok(typeof status.balance === "number");
54
- assert.ok(status.balance >= 0);
55
- assert.ok(Array.isArray(status.openTabs));
29
+ assert.ok(status.balance.total >= 0);
30
+ assert.ok(typeof status.openTabs === "number");
56
31
  });
57
32
 
58
- it("rejects request without auth headers (raw fetch)", async () => {
59
- const resp = await fetch(`${TESTNET_URL}/status`);
60
- assert.equal(resp.status, 400, "should reject unauthenticated request");
61
- const body = (await resp.json()) as { error: string };
62
- assert.equal(body.error, "auth_missing");
33
+ it("balance returns total/locked/available", async () => {
34
+ const bal = await wallet.balance();
35
+ assert.ok(typeof bal.total === "number");
36
+ assert.ok(typeof bal.locked === "number");
37
+ assert.ok(typeof bal.available === "number");
38
+ assert.ok(bal.available <= bal.total);
63
39
  });
64
40
  });
65
41
 
66
- // ── Mint (Testnet Faucet) ──────────────────────────────────────────
67
-
68
42
  describe("E2E: Mint testnet USDC", { skip }, () => {
69
- let client: PayClient;
43
+ let wallet: Wallet;
44
+ before(() => { wallet = makeWallet(); });
70
45
 
71
- before(() => {
72
- client = makeClient();
73
- });
74
-
75
- it("mints $10 USDC to the authenticated wallet", async () => {
76
- const headers = await buildAuthHeaders(
77
- TESTNET_KEY as Hex,
78
- "POST",
79
- "/api/v1/mint",
80
- { chainId: CHAIN_ID, routerAddress: ROUTER_ADDRESS }
81
- );
82
- const resp = await fetch(`${TESTNET_URL}/mint`, {
83
- method: "POST",
84
- headers: { "Content-Type": "application/json", ...headers },
85
- body: JSON.stringify({ amount: 10_000_000 }),
86
- });
87
- const bodyText = await resp.text();
88
- assert.equal(resp.status, 200, `mint failed: ${bodyText}`);
89
- const body = JSON.parse(bodyText) as { tx_hash: string; amount: number; to: string };
90
- assert.ok(body.tx_hash, "should return a tx_hash");
91
- assert.equal(body.amount, 10_000_000);
92
- assert.ok(body.to.startsWith("0x"));
46
+ it("mints $10 USDC", async () => {
47
+ const result = await wallet.mint(10);
48
+ assert.ok(result.txHash);
49
+ assert.equal(result.amount, 10);
93
50
  });
94
51
  });
95
52
 
96
- // ── Webhook CRUD ───────────────────────────────────────────────────
97
-
98
- describe("E2E: Webhook CRUD with real auth", { skip }, () => {
99
- let client: PayClient;
100
- let whId = "";
101
-
102
- before(() => {
103
- client = makeClient();
104
- });
53
+ describe("E2E: Webhook CRUD", { skip }, () => {
54
+ let wallet: Wallet;
55
+ let hookId = "";
56
+ before(() => { wallet = makeWallet(); });
105
57
 
106
58
  it("registers a webhook", async () => {
107
59
  const slug = randomUUID().slice(0, 8);
108
- const wh = await client.registerWebhook(
60
+ const wh = await wallet.registerWebhook(
109
61
  `https://example.com/hook/${slug}`,
110
- {
111
- events: ["payment.completed"],
112
- secret: `whsec_test_${slug}`,
113
- }
62
+ ["payment.completed"],
63
+ `whsec_test_${slug}`,
114
64
  );
115
- assert.ok(wh.webhookId);
65
+ assert.ok(wh.id);
116
66
  assert.ok(wh.url.startsWith("https://"));
117
- assert.ok(wh.events.includes("payment.completed"));
118
- whId = wh.webhookId;
67
+ hookId = wh.id;
119
68
  });
120
69
 
121
- it("lists webhooks including the new one", async () => {
122
- const webhooks = await client.listWebhooks();
123
- assert.ok(Array.isArray(webhooks));
124
- const ids = webhooks.map((w: WebhookRegistration) => w.webhookId);
125
- assert.ok(ids.includes(whId));
70
+ it("lists webhooks", async () => {
71
+ const hooks = await wallet.listWebhooks();
72
+ assert.ok(hooks.some((h) => h.id === hookId));
126
73
  });
127
74
 
128
75
  it("deletes the webhook", async () => {
129
- await client.deleteWebhook(whId);
130
- const webhooks = await client.listWebhooks();
131
- const ids = webhooks.map((w: WebhookRegistration) => w.webhookId);
132
- assert.ok(!ids.includes(whId));
76
+ await wallet.deleteWebhook(hookId);
77
+ const hooks = await wallet.listWebhooks();
78
+ assert.ok(!hooks.some((h) => h.id === hookId));
133
79
  });
134
80
  });
135
81
 
136
- // ── Client-side validation still works ─────────────────────────────
137
-
138
- describe("E2E: Client validation", { skip }, () => {
139
- let client: PayClient;
140
-
141
- before(() => {
142
- client = makeClient();
143
- });
82
+ describe("E2E: Validation still works", { skip }, () => {
83
+ let wallet: Wallet;
84
+ before(() => { wallet = makeWallet(); });
144
85
 
145
86
  it("rejects invalid address", async () => {
146
87
  await assert.rejects(
147
- () => client.payDirect("not-an-address", 1_000_000),
148
- (err: unknown) => err instanceof PayValidationError
88
+ () => wallet.send("not-an-address", 5),
89
+ PayValidationError,
149
90
  );
150
91
  });
151
92
 
152
93
  it("rejects amount below minimum", async () => {
153
94
  await assert.rejects(
154
- () => client.payDirect("0x" + "a1".repeat(20), 500_000),
155
- (err: unknown) => err instanceof PayValidationError
95
+ () => wallet.send("0x" + "a1".repeat(20), 0.5),
96
+ PayValidationError,
156
97
  );
157
98
  });
158
99
  });
@@ -13,7 +13,7 @@ describe("error hierarchy", () => {
13
13
  assert.ok(new PayValidationError("x") instanceof PayError);
14
14
  assert.ok(new PayNetworkError("x") instanceof PayError);
15
15
  assert.ok(new PayServerError("x", 400) instanceof PayError);
16
- assert.ok(new PayInsufficientFundsError() instanceof PayError);
16
+ assert.ok(new PayInsufficientFundsError("x") instanceof PayError);
17
17
  });
18
18
 
19
19
  it("PayValidationError has field and code", () => {
@@ -33,4 +33,12 @@ describe("error hierarchy", () => {
33
33
  const err = new PayNetworkError("connection refused");
34
34
  assert.equal(err.code, "network_error");
35
35
  });
36
+
37
+ it("PayInsufficientFundsError includes fund link hint", () => {
38
+ const err = new PayInsufficientFundsError("low balance", 5, 10);
39
+ assert.ok(err.message.includes("createFundLink"));
40
+ assert.equal(err.balance, 5);
41
+ assert.equal(err.required, 10);
42
+ assert.equal(err.code, "insufficient_funds");
43
+ });
36
44
  });
@@ -0,0 +1,153 @@
1
+ /**
2
+ * OWS (Open Wallet Standard) integration tests.
3
+ * Uses a mock OWS module — no real @open-wallet-standard/core needed.
4
+ */
5
+
6
+ import { describe, it } from "node:test";
7
+ import assert from "node:assert/strict";
8
+ import { Wallet, PayError } from "../src/index.js";
9
+
10
+ // ── Mock OWS module ──────────────────────────────────────────────────
11
+
12
+ interface SignCall {
13
+ wallet: string;
14
+ chain: string;
15
+ json: string;
16
+ passphrase?: string;
17
+ }
18
+
19
+ function createMockOws(options?: {
20
+ accounts?: Array<{
21
+ chainId: string;
22
+ address: string;
23
+ derivationPath: string;
24
+ }>;
25
+ signature?: string;
26
+ recoveryId?: number;
27
+ }) {
28
+ const calls: SignCall[] = [];
29
+ const accounts = options?.accounts ?? [
30
+ {
31
+ chainId: "eip155:8453",
32
+ address: "0x1234567890abcdef1234567890abcdef12345678",
33
+ derivationPath: "m/44'/60'/0'/0/0",
34
+ },
35
+ ];
36
+
37
+ // Default: 65-byte hex sig (r+s+v)
38
+ const sig = options?.signature ?? "ab".repeat(64) + "1b";
39
+
40
+ return {
41
+ calls,
42
+ getWallet(nameOrId: string) {
43
+ return {
44
+ id: `id-${nameOrId}`,
45
+ name: nameOrId,
46
+ accounts,
47
+ createdAt: "2026-04-01T00:00:00Z",
48
+ };
49
+ },
50
+ signTypedData(
51
+ wallet: string,
52
+ chain: string,
53
+ typedDataJson: string,
54
+ passphrase?: string,
55
+ ) {
56
+ calls.push({ wallet, chain, json: typedDataJson, passphrase });
57
+ return {
58
+ signature: sig,
59
+ recoveryId: options?.recoveryId,
60
+ };
61
+ },
62
+ };
63
+ }
64
+
65
+ // ── Construction ──────────────────────────────────────────────────────
66
+
67
+ describe("Wallet.fromOws construction", () => {
68
+ it("creates wallet with correct address from mock OWS", async () => {
69
+ const ows = createMockOws();
70
+ const wallet = await Wallet.fromOws({
71
+ walletId: "test-agent",
72
+ _owsModule: ows,
73
+ });
74
+ assert.equal(
75
+ wallet.address,
76
+ "0x1234567890abcdef1234567890abcdef12345678",
77
+ );
78
+ });
79
+
80
+ it("finds evm chain account", async () => {
81
+ const ows = createMockOws({
82
+ accounts: [
83
+ {
84
+ chainId: "evm",
85
+ address: "0xaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
86
+ derivationPath: "m/44'/60'/0'/0/0",
87
+ },
88
+ ],
89
+ });
90
+ const wallet = await Wallet.fromOws({
91
+ walletId: "test",
92
+ _owsModule: ows,
93
+ });
94
+ assert.equal(
95
+ wallet.address,
96
+ "0xaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
97
+ );
98
+ });
99
+
100
+ it("throws when no EVM account found", async () => {
101
+ const ows = createMockOws({
102
+ accounts: [
103
+ {
104
+ chainId: "solana",
105
+ address: "SoLaNa...",
106
+ derivationPath: "m/44'/501'/0'",
107
+ },
108
+ ],
109
+ });
110
+ await assert.rejects(
111
+ () => Wallet.fromOws({ walletId: "test", _owsModule: ows }),
112
+ PayError,
113
+ );
114
+ });
115
+
116
+ it("throws when OWS module not installed (no mock)", async () => {
117
+ await assert.rejects(
118
+ () =>
119
+ Wallet.fromOws({
120
+ walletId: "test",
121
+ // _owsModule not provided -> tries dynamic import, fails
122
+ }),
123
+ PayError,
124
+ );
125
+ });
126
+
127
+ it("passes owsApiKey to signTypedData", async () => {
128
+ const ows = createMockOws();
129
+ const wallet = await Wallet.fromOws({
130
+ walletId: "agent-1",
131
+ owsApiKey: "secret-key",
132
+ _owsModule: ows,
133
+ });
134
+ // Trigger a signing call (will fail on network but the mock captures the call)
135
+ // We can't easily trigger signing without a server, so just verify construction
136
+ assert.equal(wallet.address, "0x1234567890abcdef1234567890abcdef12345678");
137
+ });
138
+ });
139
+
140
+ // ── Serialization safety ──────────────────────────────────────────────
141
+
142
+ describe("Wallet.fromOws does not leak keys", () => {
143
+ it("JSON.stringify does not expose private fields", async () => {
144
+ const ows = createMockOws();
145
+ const wallet = await Wallet.fromOws({
146
+ walletId: "secret-agent",
147
+ _owsModule: ows,
148
+ });
149
+ const json = JSON.stringify(wallet);
150
+ assert.ok(!json.includes("secret-agent"));
151
+ assert.ok(!json.includes("signTypedData"));
152
+ });
153
+ });
@@ -0,0 +1,194 @@
1
+ import { describe, it, beforeEach, afterEach } from "node:test";
2
+ import assert from "node:assert/strict";
3
+ import type { Hex } from "viem";
4
+ import { Wallet, PayError, PayValidationError } from "../src/index.js";
5
+
6
+ // Anvil account #0 — well-known test key
7
+ const ANVIL_PK =
8
+ "0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80";
9
+ const ANVIL_ADDRESS = "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266";
10
+ const VALID_ADDR = "0x" + "a1".repeat(20);
11
+
12
+ // Save/restore env for tests that modify it
13
+ let savedEnv: Record<string, string | undefined>;
14
+
15
+ function saveEnv() {
16
+ savedEnv = {
17
+ PAYSKILL_KEY: process.env.PAYSKILL_KEY,
18
+ PAYSKILL_TESTNET: process.env.PAYSKILL_TESTNET,
19
+ PAYSKILL_API_URL: process.env.PAYSKILL_API_URL,
20
+ };
21
+ }
22
+
23
+ function restoreEnv() {
24
+ for (const [k, v] of Object.entries(savedEnv)) {
25
+ if (v === undefined) delete process.env[k];
26
+ else process.env[k] = v;
27
+ }
28
+ }
29
+
30
+ // ── Construction ──────────────────────────────────────────────────────
31
+
32
+ describe("Wallet construction", () => {
33
+ beforeEach(saveEnv);
34
+ afterEach(restoreEnv);
35
+
36
+ it("constructs with explicit private key", () => {
37
+ const wallet = new Wallet({ privateKey: ANVIL_PK });
38
+ assert.equal(wallet.address, ANVIL_ADDRESS);
39
+ });
40
+
41
+ it("constructs without 0x prefix", () => {
42
+ const wallet = new Wallet({ privateKey: ANVIL_PK.slice(2) });
43
+ assert.equal(wallet.address, ANVIL_ADDRESS);
44
+ });
45
+
46
+ it("constructs from PAYSKILL_KEY env var", () => {
47
+ process.env.PAYSKILL_KEY = ANVIL_PK;
48
+ const wallet = new Wallet();
49
+ assert.equal(wallet.address, ANVIL_ADDRESS);
50
+ });
51
+
52
+ it("throws without key", () => {
53
+ delete process.env.PAYSKILL_KEY;
54
+ assert.throws(() => new Wallet(), PayError);
55
+ });
56
+
57
+ it("throws on invalid key (too short)", () => {
58
+ assert.throws(
59
+ () => new Wallet({ privateKey: "0xdead" }),
60
+ PayValidationError,
61
+ );
62
+ });
63
+
64
+ it("throws on invalid key (non-hex)", () => {
65
+ assert.throws(
66
+ () => new Wallet({ privateKey: "0x" + "zz".repeat(32) }),
67
+ PayValidationError,
68
+ );
69
+ });
70
+
71
+ it("Wallet.fromEnv reads PAYSKILL_KEY", () => {
72
+ process.env.PAYSKILL_KEY = ANVIL_PK;
73
+ const wallet = Wallet.fromEnv();
74
+ assert.equal(wallet.address, ANVIL_ADDRESS);
75
+ });
76
+
77
+ it("Wallet.fromEnv throws without env var", () => {
78
+ delete process.env.PAYSKILL_KEY;
79
+ assert.throws(() => Wallet.fromEnv(), PayError);
80
+ });
81
+
82
+ it("Wallet.create falls back to env var when keychain unavailable", async () => {
83
+ process.env.PAYSKILL_KEY = ANVIL_PK;
84
+ const wallet = await Wallet.create();
85
+ assert.equal(wallet.address, ANVIL_ADDRESS);
86
+ });
87
+
88
+ it("respects testnet option", () => {
89
+ process.env.PAYSKILL_KEY = ANVIL_PK;
90
+ // Just verify it doesn't throw — testnet flag affects API URL only
91
+ const wallet = new Wallet({ testnet: true });
92
+ assert.equal(wallet.address, ANVIL_ADDRESS);
93
+ });
94
+
95
+ it("respects PAYSKILL_TESTNET env var", () => {
96
+ process.env.PAYSKILL_KEY = ANVIL_PK;
97
+ process.env.PAYSKILL_TESTNET = "1";
98
+ const wallet = new Wallet();
99
+ assert.equal(wallet.address, ANVIL_ADDRESS);
100
+ });
101
+ });
102
+
103
+ // ── Validation ──────────────────────────────────────────────────────
104
+
105
+ describe("Wallet input validation", () => {
106
+ let wallet: Wallet;
107
+
108
+ beforeEach(() => {
109
+ wallet = new Wallet({ privateKey: ANVIL_PK });
110
+ });
111
+
112
+ it("send rejects invalid address", async () => {
113
+ await assert.rejects(
114
+ () => wallet.send("not-an-address", 5),
115
+ PayValidationError,
116
+ );
117
+ });
118
+
119
+ it("send rejects amount below $1 minimum", async () => {
120
+ await assert.rejects(
121
+ () => wallet.send(VALID_ADDR, 0.5),
122
+ PayValidationError,
123
+ );
124
+ });
125
+
126
+ it("send rejects negative amount", async () => {
127
+ await assert.rejects(
128
+ () => wallet.send(VALID_ADDR, -5),
129
+ PayValidationError,
130
+ );
131
+ });
132
+
133
+ it("send rejects NaN", async () => {
134
+ await assert.rejects(
135
+ () => wallet.send(VALID_ADDR, NaN),
136
+ PayValidationError,
137
+ );
138
+ });
139
+
140
+ it("send rejects Infinity", async () => {
141
+ await assert.rejects(
142
+ () => wallet.send(VALID_ADDR, Infinity),
143
+ PayValidationError,
144
+ );
145
+ });
146
+
147
+ it("openTab rejects amount below $5 minimum", async () => {
148
+ await assert.rejects(
149
+ () => wallet.openTab(VALID_ADDR, 3, 1),
150
+ PayValidationError,
151
+ );
152
+ });
153
+
154
+ it("openTab rejects zero maxChargePerCall", async () => {
155
+ await assert.rejects(
156
+ () => wallet.openTab(VALID_ADDR, 10, 0),
157
+ PayValidationError,
158
+ );
159
+ });
160
+
161
+ it("openTab rejects invalid provider address", async () => {
162
+ await assert.rejects(
163
+ () => wallet.openTab("bad-addr", 10, 1),
164
+ PayValidationError,
165
+ );
166
+ });
167
+
168
+ it("mint rejects on mainnet", async () => {
169
+ await assert.rejects(() => wallet.mint(10), PayError);
170
+ });
171
+
172
+ it("micro amount conversion works", async () => {
173
+ // $5 micro amount should not fail validation for tab (need network for full test)
174
+ await assert.rejects(
175
+ () => wallet.openTab(VALID_ADDR, { micro: 5_000_000 }, { micro: 100_000 }),
176
+ // Will fail on network (can't reach server), not validation
177
+ (err: Error) => !(err instanceof PayValidationError),
178
+ );
179
+ });
180
+
181
+ it("micro amount rejects negative", async () => {
182
+ await assert.rejects(
183
+ () => wallet.send(VALID_ADDR, { micro: -1 }),
184
+ PayValidationError,
185
+ );
186
+ });
187
+
188
+ it("micro amount rejects non-integer", async () => {
189
+ await assert.rejects(
190
+ () => wallet.send(VALID_ADDR, { micro: 1.5 }),
191
+ PayValidationError,
192
+ );
193
+ });
194
+ });
package/dist/client.d.ts DELETED
@@ -1,94 +0,0 @@
1
- /**
2
- * PayClient — single entry point for the pay SDK.
3
- */
4
- import type { DirectPaymentResult, DiscoverOptions, DiscoverService, StatusResponse, Tab, WebhookRegistration } from "./models.js";
5
- import type { Signer } from "./signer.js";
6
- export declare const DEFAULT_API_URL = "https://pay-skill.com/api/v1";
7
- export interface PayClientOptions {
8
- apiUrl?: string;
9
- signer?: Signer | "cli" | "raw" | "custom";
10
- signerOptions?: {
11
- command?: string;
12
- key?: string;
13
- address?: string;
14
- callback?: (hash: Uint8Array) => Uint8Array;
15
- };
16
- /** Private key for direct auth signing (alternative to signer). */
17
- privateKey?: string;
18
- /** Chain ID for EIP-712 domain (default: 8453 for Base). */
19
- chainId?: number;
20
- /** Router contract address for EIP-712 domain. */
21
- routerAddress?: string;
22
- }
23
- export declare class PayClient {
24
- private readonly apiUrl;
25
- /** URL path prefix extracted from apiUrl (e.g., "/api/v1"). */
26
- private readonly _basePath;
27
- private readonly signer;
28
- private readonly _privateKey;
29
- private readonly _authConfig;
30
- private readonly _chainId;
31
- private readonly _address;
32
- constructor(options?: PayClientOptions);
33
- private getContracts;
34
- payDirect(to: string, amount: number, options?: {
35
- memo?: string;
36
- }): Promise<DirectPaymentResult>;
37
- openTab(provider: string, amount: number, options: {
38
- maxChargePerCall: number;
39
- }): Promise<Tab>;
40
- closeTab(tabId: string): Promise<Tab>;
41
- topUpTab(tabId: string, amount: number): Promise<Tab>;
42
- listTabs(): Promise<Tab[]>;
43
- getTab(tabId: string): Promise<Tab>;
44
- private static readonly X402_TAB_MULTIPLIER;
45
- request(url: string, options?: {
46
- method?: string;
47
- body?: unknown;
48
- headers?: Record<string, string>;
49
- }): Promise<Response>;
50
- /**
51
- * Parse x402 V2 payment requirements from a 402 response.
52
- *
53
- * Checks PAYMENT-REQUIRED header first (base64-encoded JSON),
54
- * falls back to response body for requirements.
55
- */
56
- private parse402Requirements;
57
- private static extractRequirements;
58
- private handle402;
59
- private settleViaDirect;
60
- private settleViaTab;
61
- getStatus(): Promise<StatusResponse>;
62
- registerWebhook(url: string, options?: {
63
- events?: string[];
64
- secret?: string;
65
- }): Promise<WebhookRegistration>;
66
- listWebhooks(): Promise<WebhookRegistration[]>;
67
- deleteWebhook(webhookId: string): Promise<void>;
68
- /** Create a one-time fund link via the server. Returns the dashboard URL. */
69
- createFundLink(options?: {
70
- messages?: unknown[];
71
- agentName?: string;
72
- }): Promise<string>;
73
- /** Create a one-time withdraw link via the server. Returns the dashboard URL. */
74
- createWithdrawLink(options?: {
75
- messages?: unknown[];
76
- agentName?: string;
77
- }): Promise<string>;
78
- /** Search for discoverable paid API services. Public, no auth required. */
79
- discover(options?: DiscoverOptions): Promise<DiscoverService[]>;
80
- /**
81
- * Prepare and sign a USDC EIP-2612 permit.
82
- *
83
- * 1. Calls GET /api/v1/permit/prepare to get the EIP-712 hash
84
- * 2. Signs the hash with the agent's private key
85
- * 3. Returns {nonce, deadline, v, r, s} for inclusion in payment body
86
- */
87
- private prepareAndSignPermit;
88
- private authHeaders;
89
- private get;
90
- private post;
91
- private del;
92
- private handleResponse;
93
- }
94
- //# sourceMappingURL=client.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"client.d.ts","sourceRoot":"","sources":["../src/client.ts"],"names":[],"mappings":"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"}