@pay-skill/sdk 0.1.1 → 0.1.3

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/src/signer.ts CHANGED
@@ -1,147 +1,147 @@
1
- /**
2
- * Signer interface and implementations.
3
- *
4
- * Three modes:
5
- * 1. CLI signer (default): subprocess call to `pay sign`
6
- * 2. Raw key: from PAYSKILL_KEY environment variable (dev/testing only)
7
- * 3. Custom: user provides a sign(hash) -> signature callback
8
- */
9
-
10
- import { execFileSync } from "node:child_process";
11
- import { type Hex } from "viem";
12
- import {
13
- privateKeyToAccount,
14
- sign as viemSign,
15
- serializeSignature,
16
- } from "viem/accounts";
17
-
18
- /** Abstract signer interface. */
19
- export interface Signer {
20
- /** Sign a 32-byte hash. Returns 65-byte signature (r || s || v). */
21
- sign(hash: Uint8Array): Uint8Array;
22
- /** The signer's Ethereum address (0x-prefixed, checksummed). */
23
- readonly address: string;
24
- }
25
-
26
- /** Signs via the `pay sign` CLI subprocess. */
27
- export class CliSigner implements Signer {
28
- private readonly command: string;
29
- readonly address: string;
30
-
31
- constructor(command = "pay", address = "") {
32
- this.command = command;
33
- if (address) {
34
- this.address = address;
35
- } else {
36
- try {
37
- this.address = execFileSync(this.command, ["address"], {
38
- encoding: "utf-8",
39
- timeout: 10_000,
40
- }).trim();
41
- } catch {
42
- this.address = "";
43
- }
44
- }
45
- }
46
-
47
- sign(hash: Uint8Array): Uint8Array {
48
- const hexHash = Buffer.from(hash).toString("hex");
49
- const result = execFileSync(this.command, ["sign"], {
50
- input: hexHash,
51
- encoding: "utf-8",
52
- timeout: 30_000,
53
- });
54
- return Buffer.from(result.trim(), "hex");
55
- }
56
- }
57
-
58
- /** Signs with a raw private key using viem. Dev/testing only. */
59
- export class RawKeySigner implements Signer {
60
- private readonly _key: Hex;
61
- readonly address: string;
62
-
63
- constructor(key?: string) {
64
- const rawKey = key ?? process.env.PAYSKILL_KEY ?? "";
65
- if (!rawKey) {
66
- throw new Error("No key provided and PAYSKILL_KEY not set");
67
- }
68
- this._key = rawKey.startsWith("0x")
69
- ? (rawKey as Hex)
70
- : (`0x${rawKey}` as Hex);
71
- const account = privateKeyToAccount(this._key);
72
- this.address = account.address;
73
- }
74
-
75
- sign(_hash: Uint8Array): Uint8Array {
76
- // viem's sign() does raw ECDSA signing (no EIP-191 prefix)
77
- // sign() is async but Signer interface is sync — we use the sync internal
78
- // For the sync interface, we need a workaround. Since RawKeySigner is
79
- // dev/testing only, we'll do a sync computation via signSync.
80
- // viem doesn't expose sync sign, but we can construct manually.
81
- // Use the async path via signAsync helper pattern.
82
- throw new Error(
83
- "RawKeySigner.sign() is synchronous but viem signing is async. " +
84
- "Use RawKeySigner.signAsync() or use buildAuthHeaders() directly with the private key."
85
- );
86
- }
87
-
88
- /** Async sign — preferred over sync sign(). */
89
- async signAsync(hash: Uint8Array): Promise<Uint8Array> {
90
- const hashHex = ("0x" + Buffer.from(hash).toString("hex")) as Hex;
91
- const raw = await viemSign({ hash: hashHex, privateKey: this._key });
92
- const sigHex = serializeSignature(raw);
93
- return hexToBytes(sigHex);
94
- }
95
- }
96
-
97
- /** Delegates signing to a user-provided callback. */
98
- export class CallbackSigner implements Signer {
99
- private readonly callback: (hash: Uint8Array) => Uint8Array;
100
- readonly address: string;
101
-
102
- constructor(
103
- callback: (hash: Uint8Array) => Uint8Array,
104
- address = ""
105
- ) {
106
- this.callback = callback;
107
- this.address = address;
108
- }
109
-
110
- sign(hash: Uint8Array): Uint8Array {
111
- return this.callback(hash);
112
- }
113
- }
114
-
115
- /** Factory for creating signers. */
116
- export function createSigner(
117
- mode: "cli" | "raw" | "custom" = "cli",
118
- options: {
119
- command?: string;
120
- key?: string;
121
- address?: string;
122
- callback?: (hash: Uint8Array) => Uint8Array;
123
- } = {}
124
- ): Signer {
125
- switch (mode) {
126
- case "cli":
127
- return new CliSigner(options.command, options.address);
128
- case "raw":
129
- return new RawKeySigner(options.key);
130
- case "custom":
131
- if (!options.callback) {
132
- throw new Error("custom signer requires a callback");
133
- }
134
- return new CallbackSigner(options.callback, options.address);
135
- default:
136
- throw new Error(`Unknown signer mode: ${mode as string}`);
137
- }
138
- }
139
-
140
- function hexToBytes(hex: string): Uint8Array {
141
- const clean = hex.startsWith("0x") ? hex.slice(2) : hex;
142
- const bytes = new Uint8Array(clean.length / 2);
143
- for (let i = 0; i < bytes.length; i++) {
144
- bytes[i] = parseInt(clean.slice(i * 2, i * 2 + 2), 16);
145
- }
146
- return bytes;
147
- }
1
+ /**
2
+ * Signer interface and implementations.
3
+ *
4
+ * Three modes:
5
+ * 1. CLI signer (default): subprocess call to `pay sign`
6
+ * 2. Raw key: from PAYSKILL_KEY environment variable (dev/testing only)
7
+ * 3. Custom: user provides a sign(hash) -> signature callback
8
+ */
9
+
10
+ import { execFileSync } from "node:child_process";
11
+ import { type Hex } from "viem";
12
+ import {
13
+ privateKeyToAccount,
14
+ sign as viemSign,
15
+ serializeSignature,
16
+ } from "viem/accounts";
17
+
18
+ /** Abstract signer interface. */
19
+ export interface Signer {
20
+ /** Sign a 32-byte hash. Returns 65-byte signature (r || s || v). */
21
+ sign(hash: Uint8Array): Uint8Array;
22
+ /** The signer's Ethereum address (0x-prefixed, checksummed). */
23
+ readonly address: string;
24
+ }
25
+
26
+ /** Signs via the `pay sign` CLI subprocess. */
27
+ export class CliSigner implements Signer {
28
+ private readonly command: string;
29
+ readonly address: string;
30
+
31
+ constructor(command = "pay", address = "") {
32
+ this.command = command;
33
+ if (address) {
34
+ this.address = address;
35
+ } else {
36
+ try {
37
+ this.address = execFileSync(this.command, ["address"], {
38
+ encoding: "utf-8",
39
+ timeout: 10_000,
40
+ }).trim();
41
+ } catch {
42
+ this.address = "";
43
+ }
44
+ }
45
+ }
46
+
47
+ sign(hash: Uint8Array): Uint8Array {
48
+ const hexHash = Buffer.from(hash).toString("hex");
49
+ const result = execFileSync(this.command, ["sign"], {
50
+ input: hexHash,
51
+ encoding: "utf-8",
52
+ timeout: 30_000,
53
+ });
54
+ return Buffer.from(result.trim(), "hex");
55
+ }
56
+ }
57
+
58
+ /** Signs with a raw private key using viem. Dev/testing only. */
59
+ export class RawKeySigner implements Signer {
60
+ private readonly _key: Hex;
61
+ readonly address: string;
62
+
63
+ constructor(key?: string) {
64
+ const rawKey = key ?? process.env.PAYSKILL_KEY ?? "";
65
+ if (!rawKey) {
66
+ throw new Error("No key provided and PAYSKILL_KEY not set");
67
+ }
68
+ this._key = rawKey.startsWith("0x")
69
+ ? (rawKey as Hex)
70
+ : (`0x${rawKey}` as Hex);
71
+ const account = privateKeyToAccount(this._key);
72
+ this.address = account.address;
73
+ }
74
+
75
+ sign(_hash: Uint8Array): Uint8Array {
76
+ // viem's sign() does raw ECDSA signing (no EIP-191 prefix)
77
+ // sign() is async but Signer interface is sync — we use the sync internal
78
+ // For the sync interface, we need a workaround. Since RawKeySigner is
79
+ // dev/testing only, we'll do a sync computation via signSync.
80
+ // viem doesn't expose sync sign, but we can construct manually.
81
+ // Use the async path via signAsync helper pattern.
82
+ throw new Error(
83
+ "RawKeySigner.sign() is synchronous but viem signing is async. " +
84
+ "Use RawKeySigner.signAsync() or use buildAuthHeaders() directly with the private key."
85
+ );
86
+ }
87
+
88
+ /** Async sign — preferred over sync sign(). */
89
+ async signAsync(hash: Uint8Array): Promise<Uint8Array> {
90
+ const hashHex = ("0x" + Buffer.from(hash).toString("hex")) as Hex;
91
+ const raw = await viemSign({ hash: hashHex, privateKey: this._key });
92
+ const sigHex = serializeSignature(raw);
93
+ return hexToBytes(sigHex);
94
+ }
95
+ }
96
+
97
+ /** Delegates signing to a user-provided callback. */
98
+ export class CallbackSigner implements Signer {
99
+ private readonly callback: (hash: Uint8Array) => Uint8Array;
100
+ readonly address: string;
101
+
102
+ constructor(
103
+ callback: (hash: Uint8Array) => Uint8Array,
104
+ address = ""
105
+ ) {
106
+ this.callback = callback;
107
+ this.address = address;
108
+ }
109
+
110
+ sign(hash: Uint8Array): Uint8Array {
111
+ return this.callback(hash);
112
+ }
113
+ }
114
+
115
+ /** Factory for creating signers. */
116
+ export function createSigner(
117
+ mode: "cli" | "raw" | "custom" = "cli",
118
+ options: {
119
+ command?: string;
120
+ key?: string;
121
+ address?: string;
122
+ callback?: (hash: Uint8Array) => Uint8Array;
123
+ } = {}
124
+ ): Signer {
125
+ switch (mode) {
126
+ case "cli":
127
+ return new CliSigner(options.command, options.address);
128
+ case "raw":
129
+ return new RawKeySigner(options.key);
130
+ case "custom":
131
+ if (!options.callback) {
132
+ throw new Error("custom signer requires a callback");
133
+ }
134
+ return new CallbackSigner(options.callback, options.address);
135
+ default:
136
+ throw new Error(`Unknown signer mode: ${mode as string}`);
137
+ }
138
+ }
139
+
140
+ function hexToBytes(hex: string): Uint8Array {
141
+ const clean = hex.startsWith("0x") ? hex.slice(2) : hex;
142
+ const bytes = new Uint8Array(clean.length / 2);
143
+ for (let i = 0; i < bytes.length; i++) {
144
+ bytes[i] = parseInt(clean.slice(i * 2, i * 2 + 2), 16);
145
+ }
146
+ return bytes;
147
+ }