@buildersgarden/siwa 0.0.3 → 0.0.5
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/dist/keystore.d.ts +2 -2
- package/dist/keystore.js +165 -96
- package/package.json +11 -6
- package/LICENSE +0 -21
package/dist/keystore.d.ts
CHANGED
|
@@ -34,8 +34,8 @@
|
|
|
34
34
|
* KEYSTORE_PATH — Path to encrypted keystore file (default: ./agent-keystore.json)
|
|
35
35
|
* AGENT_PRIVATE_KEY — Fallback for env backend only
|
|
36
36
|
*/
|
|
37
|
-
import { type WalletClient, type Account, type Chain, type Transport } from
|
|
38
|
-
export type KeystoreBackend =
|
|
37
|
+
import { type WalletClient, type Account, type Chain, type Transport } from "viem";
|
|
38
|
+
export type KeystoreBackend = "encrypted-file" | "env" | "proxy";
|
|
39
39
|
export interface KeystoreConfig {
|
|
40
40
|
backend?: KeystoreBackend;
|
|
41
41
|
keystorePath?: string;
|
package/dist/keystore.js
CHANGED
|
@@ -34,20 +34,20 @@
|
|
|
34
34
|
* KEYSTORE_PATH — Path to encrypted keystore file (default: ./agent-keystore.json)
|
|
35
35
|
* AGENT_PRIVATE_KEY — Fallback for env backend only
|
|
36
36
|
*/
|
|
37
|
-
import { createWalletClient, http, keccak256, toBytes, toHex, concat, } from
|
|
38
|
-
import { privateKeyToAccount, generatePrivateKey } from
|
|
39
|
-
import { hashAuthorization } from
|
|
40
|
-
import { scrypt } from
|
|
41
|
-
import { randomBytes } from
|
|
42
|
-
import { ctr } from
|
|
43
|
-
import * as fs from
|
|
44
|
-
import * as crypto from
|
|
45
|
-
import * as os from
|
|
46
|
-
import { computeHmac } from
|
|
37
|
+
import { createWalletClient, http, keccak256, toBytes, toHex, concat, } from "viem";
|
|
38
|
+
import { privateKeyToAccount, generatePrivateKey } from "viem/accounts";
|
|
39
|
+
import { hashAuthorization } from "viem/experimental";
|
|
40
|
+
import { scrypt } from "@noble/hashes/scrypt";
|
|
41
|
+
import { randomBytes } from "@noble/hashes/utils";
|
|
42
|
+
import { ctr } from "@noble/ciphers/aes";
|
|
43
|
+
import * as fs from "fs";
|
|
44
|
+
import * as crypto from "crypto";
|
|
45
|
+
import * as os from "os";
|
|
46
|
+
import { computeHmac } from "./proxy-auth.js";
|
|
47
47
|
// ---------------------------------------------------------------------------
|
|
48
48
|
// Constants
|
|
49
49
|
// ---------------------------------------------------------------------------
|
|
50
|
-
const DEFAULT_KEYSTORE_PATH =
|
|
50
|
+
const DEFAULT_KEYSTORE_PATH = "./agent-keystore.json";
|
|
51
51
|
function generateUUID() {
|
|
52
52
|
const bytes = randomBytes(16);
|
|
53
53
|
bytes[6] = (bytes[6] & 0x0f) | 0x40; // version 4
|
|
@@ -65,13 +65,21 @@ async function encryptKeystore(privateKey, password) {
|
|
|
65
65
|
const p = 1;
|
|
66
66
|
const dklen = 32;
|
|
67
67
|
// Derive key using scrypt
|
|
68
|
-
const derivedKey = scrypt(new TextEncoder().encode(password), salt, {
|
|
68
|
+
const derivedKey = scrypt(new TextEncoder().encode(password), salt, {
|
|
69
|
+
N: n,
|
|
70
|
+
r,
|
|
71
|
+
p,
|
|
72
|
+
dkLen: dklen,
|
|
73
|
+
});
|
|
69
74
|
// Encrypt private key with AES-128-CTR
|
|
70
75
|
const encryptionKey = derivedKey.slice(0, 16);
|
|
71
76
|
const cipher = ctr(encryptionKey, iv);
|
|
72
77
|
const ciphertext = cipher.encrypt(privateKeyBytes);
|
|
73
78
|
// Calculate MAC: keccak256(derivedKey[16:32] + ciphertext)
|
|
74
|
-
const macData = concat([
|
|
79
|
+
const macData = concat([
|
|
80
|
+
toHex(derivedKey.slice(16, 32)),
|
|
81
|
+
toHex(ciphertext),
|
|
82
|
+
]);
|
|
75
83
|
const mac = keccak256(macData);
|
|
76
84
|
// Get address from private key
|
|
77
85
|
const account = privateKeyToAccount(privateKey);
|
|
@@ -82,8 +90,8 @@ async function encryptKeystore(privateKey, password) {
|
|
|
82
90
|
crypto: {
|
|
83
91
|
ciphertext: toHex(ciphertext).slice(2),
|
|
84
92
|
cipherparams: { iv: toHex(iv).slice(2) },
|
|
85
|
-
cipher:
|
|
86
|
-
kdf:
|
|
93
|
+
cipher: "aes-128-ctr",
|
|
94
|
+
kdf: "scrypt",
|
|
87
95
|
kdfparams: {
|
|
88
96
|
dklen,
|
|
89
97
|
salt: toHex(salt).slice(2),
|
|
@@ -102,10 +110,10 @@ async function decryptKeystore(json, password) {
|
|
|
102
110
|
throw new Error(`Unsupported keystore version: ${keystore.version}`);
|
|
103
111
|
}
|
|
104
112
|
const { crypto: cryptoData } = keystore;
|
|
105
|
-
if (cryptoData.kdf !==
|
|
113
|
+
if (cryptoData.kdf !== "scrypt") {
|
|
106
114
|
throw new Error(`Unsupported KDF: ${cryptoData.kdf}`);
|
|
107
115
|
}
|
|
108
|
-
if (cryptoData.cipher !==
|
|
116
|
+
if (cryptoData.cipher !== "aes-128-ctr") {
|
|
109
117
|
throw new Error(`Unsupported cipher: ${cryptoData.cipher}`);
|
|
110
118
|
}
|
|
111
119
|
const { kdfparams } = cryptoData;
|
|
@@ -113,12 +121,20 @@ async function decryptKeystore(json, password) {
|
|
|
113
121
|
const iv = toBytes(`0x${cryptoData.cipherparams.iv}`);
|
|
114
122
|
const ciphertext = toBytes(`0x${cryptoData.ciphertext}`);
|
|
115
123
|
// Derive key using scrypt
|
|
116
|
-
const derivedKey = scrypt(new TextEncoder().encode(password), salt, {
|
|
124
|
+
const derivedKey = scrypt(new TextEncoder().encode(password), salt, {
|
|
125
|
+
N: kdfparams.n,
|
|
126
|
+
r: kdfparams.r,
|
|
127
|
+
p: kdfparams.p,
|
|
128
|
+
dkLen: kdfparams.dklen,
|
|
129
|
+
});
|
|
117
130
|
// Verify MAC
|
|
118
|
-
const macData = concat([
|
|
131
|
+
const macData = concat([
|
|
132
|
+
toHex(derivedKey.slice(16, 32)),
|
|
133
|
+
toHex(ciphertext),
|
|
134
|
+
]);
|
|
119
135
|
const calculatedMac = keccak256(macData).slice(2);
|
|
120
136
|
if (calculatedMac.toLowerCase() !== cryptoData.mac.toLowerCase()) {
|
|
121
|
-
throw new Error(
|
|
137
|
+
throw new Error("Invalid password or corrupted keystore");
|
|
122
138
|
}
|
|
123
139
|
// Decrypt private key with AES-128-CTR
|
|
124
140
|
const encryptionKey = derivedKey.slice(0, 16);
|
|
@@ -133,15 +149,15 @@ async function proxyRequest(config, endpoint, body = {}) {
|
|
|
133
149
|
const url = config.proxyUrl || process.env.KEYRING_PROXY_URL;
|
|
134
150
|
const secret = config.proxySecret || process.env.KEYRING_PROXY_SECRET;
|
|
135
151
|
if (!url)
|
|
136
|
-
throw new Error(
|
|
152
|
+
throw new Error("Proxy backend requires KEYRING_PROXY_URL or config.proxyUrl");
|
|
137
153
|
if (!secret)
|
|
138
|
-
throw new Error(
|
|
139
|
-
const bodyStr = JSON.stringify(body, (_key, value) => typeof value ===
|
|
140
|
-
const hmacHeaders = computeHmac(secret,
|
|
154
|
+
throw new Error("Proxy backend requires KEYRING_PROXY_SECRET or config.proxySecret");
|
|
155
|
+
const bodyStr = JSON.stringify(body, (_key, value) => typeof value === "bigint" ? "0x" + value.toString(16) : value);
|
|
156
|
+
const hmacHeaders = computeHmac(secret, "POST", endpoint, bodyStr);
|
|
141
157
|
const res = await fetch(`${url}${endpoint}`, {
|
|
142
|
-
method:
|
|
158
|
+
method: "POST",
|
|
143
159
|
headers: {
|
|
144
|
-
|
|
160
|
+
"Content-Type": "application/json",
|
|
145
161
|
...hmacHeaders,
|
|
146
162
|
},
|
|
147
163
|
body: bodyStr,
|
|
@@ -158,16 +174,16 @@ async function proxyRequest(config, endpoint, body = {}) {
|
|
|
158
174
|
export async function detectBackend() {
|
|
159
175
|
// 0. Proxy backend (if URL is set)
|
|
160
176
|
if (process.env.KEYRING_PROXY_URL)
|
|
161
|
-
return
|
|
177
|
+
return "proxy";
|
|
162
178
|
// 1. Check for existing encrypted keystore file
|
|
163
179
|
if (fs.existsSync(process.env.KEYSTORE_PATH || DEFAULT_KEYSTORE_PATH)) {
|
|
164
|
-
return
|
|
180
|
+
return "encrypted-file";
|
|
165
181
|
}
|
|
166
182
|
// 2. Check for env var
|
|
167
183
|
if (process.env.AGENT_PRIVATE_KEY)
|
|
168
|
-
return
|
|
184
|
+
return "env";
|
|
169
185
|
// 3. Default to encrypted-file (will be created on first use)
|
|
170
|
-
return
|
|
186
|
+
return "encrypted-file";
|
|
171
187
|
}
|
|
172
188
|
// ---------------------------------------------------------------------------
|
|
173
189
|
// Encrypted V3 JSON Keystore backend
|
|
@@ -179,7 +195,7 @@ async function encryptedFileStore(privateKey, password, filePath) {
|
|
|
179
195
|
async function encryptedFileLoad(password, filePath) {
|
|
180
196
|
if (!fs.existsSync(filePath))
|
|
181
197
|
return null;
|
|
182
|
-
const json = fs.readFileSync(filePath,
|
|
198
|
+
const json = fs.readFileSync(filePath, "utf-8");
|
|
183
199
|
return decryptKeystore(json, password);
|
|
184
200
|
}
|
|
185
201
|
function encryptedFileExists(filePath) {
|
|
@@ -195,15 +211,12 @@ function encryptedFileExists(filePath) {
|
|
|
195
211
|
// ---------------------------------------------------------------------------
|
|
196
212
|
function deriveMachinePassword() {
|
|
197
213
|
const factors = [
|
|
198
|
-
process.env.USER || process.env.USERNAME ||
|
|
199
|
-
process.env.HOME || process.env.USERPROFILE ||
|
|
214
|
+
process.env.USER || process.env.USERNAME || "agent",
|
|
215
|
+
process.env.HOME || process.env.USERPROFILE || "/tmp",
|
|
200
216
|
os.hostname(),
|
|
201
217
|
os.platform(),
|
|
202
218
|
];
|
|
203
|
-
return crypto
|
|
204
|
-
.createHash('sha256')
|
|
205
|
-
.update(factors.join(':'))
|
|
206
|
-
.digest('hex');
|
|
219
|
+
return crypto.createHash("sha256").update(factors.join(":")).digest("hex");
|
|
207
220
|
}
|
|
208
221
|
// ---------------------------------------------------------------------------
|
|
209
222
|
// Public API — the ONLY way external code touches private keys
|
|
@@ -213,34 +226,36 @@ function deriveMachinePassword() {
|
|
|
213
226
|
* Returns only the public address — NEVER the private key.
|
|
214
227
|
*/
|
|
215
228
|
export async function createWallet(config = {}) {
|
|
216
|
-
const backend = config.backend || await detectBackend();
|
|
229
|
+
const backend = config.backend || (await detectBackend());
|
|
217
230
|
const keystorePath = config.keystorePath || process.env.KEYSTORE_PATH || DEFAULT_KEYSTORE_PATH;
|
|
218
|
-
if (backend ===
|
|
219
|
-
const data = await proxyRequest(config,
|
|
231
|
+
if (backend === "proxy") {
|
|
232
|
+
const data = await proxyRequest(config, "/create-wallet");
|
|
220
233
|
return { address: data.address, backend, keystorePath: undefined };
|
|
221
234
|
}
|
|
222
235
|
const privateKey = generatePrivateKey();
|
|
223
236
|
const account = privateKeyToAccount(privateKey);
|
|
224
237
|
const address = account.address;
|
|
225
238
|
switch (backend) {
|
|
226
|
-
case
|
|
227
|
-
const password = config.password ||
|
|
239
|
+
case "encrypted-file": {
|
|
240
|
+
const password = config.password ||
|
|
241
|
+
process.env.KEYSTORE_PASSWORD ||
|
|
242
|
+
deriveMachinePassword();
|
|
228
243
|
await encryptedFileStore(privateKey, password, keystorePath);
|
|
229
244
|
break;
|
|
230
245
|
}
|
|
231
|
-
case
|
|
246
|
+
case "env":
|
|
232
247
|
// For env backend, we print the key ONCE for the operator to capture.
|
|
233
248
|
// This is the ONLY time the raw key is ever exposed.
|
|
234
|
-
console.log(
|
|
249
|
+
console.log("=== ENV BACKEND (testing only) ===");
|
|
235
250
|
console.log(`Set this in your environment:`);
|
|
236
251
|
console.log(` export AGENT_PRIVATE_KEY="${privateKey}"`);
|
|
237
|
-
console.log(
|
|
252
|
+
console.log("=================================");
|
|
238
253
|
break;
|
|
239
254
|
}
|
|
240
255
|
return {
|
|
241
256
|
address,
|
|
242
257
|
backend,
|
|
243
|
-
keystorePath: backend ===
|
|
258
|
+
keystorePath: backend === "encrypted-file" ? keystorePath : undefined,
|
|
244
259
|
};
|
|
245
260
|
}
|
|
246
261
|
/**
|
|
@@ -249,44 +264,46 @@ export async function createWallet(config = {}) {
|
|
|
249
264
|
* Returns only the public address.
|
|
250
265
|
*/
|
|
251
266
|
export async function importWallet(privateKey, config = {}) {
|
|
252
|
-
const backend = config.backend || await detectBackend();
|
|
253
|
-
if (backend ===
|
|
254
|
-
throw new Error(
|
|
267
|
+
const backend = config.backend || (await detectBackend());
|
|
268
|
+
if (backend === "proxy") {
|
|
269
|
+
throw new Error("importWallet() is not supported via proxy. Import the wallet on the proxy server directly.");
|
|
255
270
|
}
|
|
256
271
|
const keystorePath = config.keystorePath || process.env.KEYSTORE_PATH || DEFAULT_KEYSTORE_PATH;
|
|
257
|
-
const hexKey = (privateKey.startsWith(
|
|
272
|
+
const hexKey = (privateKey.startsWith("0x") ? privateKey : `0x${privateKey}`);
|
|
258
273
|
const account = privateKeyToAccount(hexKey);
|
|
259
274
|
const address = account.address;
|
|
260
275
|
switch (backend) {
|
|
261
|
-
case
|
|
262
|
-
const password = config.password ||
|
|
276
|
+
case "encrypted-file": {
|
|
277
|
+
const password = config.password ||
|
|
278
|
+
process.env.KEYSTORE_PASSWORD ||
|
|
279
|
+
deriveMachinePassword();
|
|
263
280
|
await encryptedFileStore(hexKey, password, keystorePath);
|
|
264
281
|
break;
|
|
265
282
|
}
|
|
266
|
-
case
|
|
283
|
+
case "env":
|
|
267
284
|
// Nothing to persist for env backend
|
|
268
285
|
break;
|
|
269
286
|
}
|
|
270
287
|
return {
|
|
271
288
|
address,
|
|
272
289
|
backend,
|
|
273
|
-
keystorePath: backend ===
|
|
290
|
+
keystorePath: backend === "encrypted-file" ? keystorePath : undefined,
|
|
274
291
|
};
|
|
275
292
|
}
|
|
276
293
|
/**
|
|
277
294
|
* Check if a wallet is available in any backend.
|
|
278
295
|
*/
|
|
279
296
|
export async function hasWallet(config = {}) {
|
|
280
|
-
const backend = config.backend || await detectBackend();
|
|
281
|
-
if (backend ===
|
|
282
|
-
const data = await proxyRequest(config,
|
|
297
|
+
const backend = config.backend || (await detectBackend());
|
|
298
|
+
if (backend === "proxy") {
|
|
299
|
+
const data = await proxyRequest(config, "/has-wallet");
|
|
283
300
|
return data.hasWallet;
|
|
284
301
|
}
|
|
285
302
|
const keystorePath = config.keystorePath || process.env.KEYSTORE_PATH || DEFAULT_KEYSTORE_PATH;
|
|
286
303
|
switch (backend) {
|
|
287
|
-
case
|
|
304
|
+
case "encrypted-file":
|
|
288
305
|
return encryptedFileExists(keystorePath);
|
|
289
|
-
case
|
|
306
|
+
case "env":
|
|
290
307
|
return !!process.env.AGENT_PRIVATE_KEY;
|
|
291
308
|
}
|
|
292
309
|
}
|
|
@@ -294,9 +311,9 @@ export async function hasWallet(config = {}) {
|
|
|
294
311
|
* Get the wallet's public address (no private key exposed).
|
|
295
312
|
*/
|
|
296
313
|
export async function getAddress(config = {}) {
|
|
297
|
-
const backend = config.backend || await detectBackend();
|
|
298
|
-
if (backend ===
|
|
299
|
-
const data = await proxyRequest(config,
|
|
314
|
+
const backend = config.backend || (await detectBackend());
|
|
315
|
+
if (backend === "proxy") {
|
|
316
|
+
const data = await proxyRequest(config, "/get-address");
|
|
300
317
|
return data.address;
|
|
301
318
|
}
|
|
302
319
|
const privateKey = await _loadPrivateKeyInternal(config);
|
|
@@ -311,51 +328,101 @@ export async function getAddress(config = {}) {
|
|
|
311
328
|
* Only the signature is returned.
|
|
312
329
|
*/
|
|
313
330
|
export async function signMessage(message, config = {}) {
|
|
314
|
-
const backend = config.backend || await detectBackend();
|
|
315
|
-
if (backend ===
|
|
316
|
-
const data = await proxyRequest(config,
|
|
331
|
+
const backend = config.backend || (await detectBackend());
|
|
332
|
+
if (backend === "proxy") {
|
|
333
|
+
const data = await proxyRequest(config, "/sign-message", { message });
|
|
317
334
|
return { signature: data.signature, address: data.address };
|
|
318
335
|
}
|
|
319
336
|
const privateKey = await _loadPrivateKeyInternal(config);
|
|
320
337
|
if (!privateKey)
|
|
321
|
-
throw new Error(
|
|
338
|
+
throw new Error("No wallet found. Run createWallet() first.");
|
|
322
339
|
const account = privateKeyToAccount(privateKey);
|
|
323
340
|
const signature = await account.signMessage({ message });
|
|
324
341
|
return { signature, address: account.address };
|
|
325
342
|
}
|
|
343
|
+
/**
|
|
344
|
+
* Parse a numeric value from JSON (string/number) to bigint.
|
|
345
|
+
* Returns undefined for null, undefined, or zero values.
|
|
346
|
+
* Zero is returned as undefined so viem encodes it as empty (0x80 in RLP).
|
|
347
|
+
*/
|
|
348
|
+
function parseBigIntFromJson(value) {
|
|
349
|
+
if (value === null || value === undefined)
|
|
350
|
+
return undefined;
|
|
351
|
+
let result;
|
|
352
|
+
if (typeof value === "bigint") {
|
|
353
|
+
result = value;
|
|
354
|
+
}
|
|
355
|
+
else if (typeof value === "number") {
|
|
356
|
+
result = BigInt(value);
|
|
357
|
+
}
|
|
358
|
+
else if (typeof value === "string") {
|
|
359
|
+
// Handle hex strings (0x...) and decimal strings
|
|
360
|
+
result = BigInt(value);
|
|
361
|
+
}
|
|
362
|
+
else {
|
|
363
|
+
return undefined;
|
|
364
|
+
}
|
|
365
|
+
// Return undefined for zero so viem encodes it as empty (0x80)
|
|
366
|
+
// instead of 0x00 which is non-canonical RLP
|
|
367
|
+
return result === 0n ? undefined : result;
|
|
368
|
+
}
|
|
369
|
+
/**
|
|
370
|
+
* Parse a numeric value, keeping zero as 0n (for fields like nonce where 0 is valid).
|
|
371
|
+
*/
|
|
372
|
+
function parseBigIntKeepZero(value) {
|
|
373
|
+
if (value === null || value === undefined)
|
|
374
|
+
return undefined;
|
|
375
|
+
if (typeof value === "bigint")
|
|
376
|
+
return value;
|
|
377
|
+
if (typeof value === "number")
|
|
378
|
+
return BigInt(value);
|
|
379
|
+
if (typeof value === "string")
|
|
380
|
+
return BigInt(value);
|
|
381
|
+
return undefined;
|
|
382
|
+
}
|
|
326
383
|
/**
|
|
327
384
|
* Sign a transaction.
|
|
328
385
|
* The private key is loaded, used, and immediately discarded.
|
|
329
386
|
* Only the signed transaction is returned.
|
|
330
387
|
*/
|
|
331
388
|
export async function signTransaction(tx, config = {}) {
|
|
332
|
-
const backend = config.backend || await detectBackend();
|
|
333
|
-
if (backend ===
|
|
334
|
-
const data = await proxyRequest(config,
|
|
389
|
+
const backend = config.backend || (await detectBackend());
|
|
390
|
+
if (backend === "proxy") {
|
|
391
|
+
const data = await proxyRequest(config, "/sign-transaction", {
|
|
392
|
+
tx: tx,
|
|
393
|
+
});
|
|
335
394
|
return { signedTx: data.signedTx, address: data.address };
|
|
336
395
|
}
|
|
337
396
|
const privateKey = await _loadPrivateKeyInternal(config);
|
|
338
397
|
if (!privateKey)
|
|
339
|
-
throw new Error(
|
|
398
|
+
throw new Error("No wallet found. Run createWallet() first.");
|
|
340
399
|
const account = privateKeyToAccount(privateKey);
|
|
400
|
+
// Parse numeric fields from JSON representation (strings) to bigints.
|
|
401
|
+
// For 'value', zero is converted to undefined so viem encodes it as 0x80 (empty)
|
|
402
|
+
// instead of 0x00, which is non-canonical RLP and rejected by nodes.
|
|
403
|
+
const value = parseBigIntFromJson(tx.value);
|
|
404
|
+
const gas = parseBigIntKeepZero(tx.gasLimit ?? tx.gas);
|
|
405
|
+
const maxFeePerGas = parseBigIntKeepZero(tx.maxFeePerGas);
|
|
406
|
+
const maxPriorityFeePerGas = parseBigIntKeepZero(tx.maxPriorityFeePerGas);
|
|
407
|
+
const gasPrice = parseBigIntKeepZero(tx.gasPrice);
|
|
341
408
|
// Build transaction request for viem
|
|
342
409
|
const viemTx = {
|
|
343
410
|
to: tx.to,
|
|
344
411
|
data: tx.data,
|
|
345
|
-
value
|
|
412
|
+
value,
|
|
346
413
|
nonce: tx.nonce,
|
|
347
414
|
chainId: tx.chainId,
|
|
348
|
-
gas
|
|
415
|
+
gas,
|
|
349
416
|
};
|
|
350
417
|
// Handle EIP-1559 vs legacy transactions
|
|
351
418
|
if (tx.type === 2 || tx.maxFeePerGas !== undefined) {
|
|
352
|
-
viemTx.type =
|
|
353
|
-
viemTx.maxFeePerGas =
|
|
354
|
-
viemTx.maxPriorityFeePerGas =
|
|
419
|
+
viemTx.type = "eip1559";
|
|
420
|
+
viemTx.maxFeePerGas = maxFeePerGas;
|
|
421
|
+
viemTx.maxPriorityFeePerGas = maxPriorityFeePerGas;
|
|
355
422
|
}
|
|
356
423
|
else if (tx.gasPrice !== undefined) {
|
|
357
|
-
viemTx.type =
|
|
358
|
-
viemTx.gasPrice =
|
|
424
|
+
viemTx.type = "legacy";
|
|
425
|
+
viemTx.gasPrice = gasPrice;
|
|
359
426
|
}
|
|
360
427
|
if (tx.accessList) {
|
|
361
428
|
viemTx.accessList = tx.accessList;
|
|
@@ -374,14 +441,14 @@ export async function signTransaction(tx, config = {}) {
|
|
|
374
441
|
* @returns Signed authorization tuple (address, nonce, chainId, yParity, r, s)
|
|
375
442
|
*/
|
|
376
443
|
export async function signAuthorization(auth, config = {}) {
|
|
377
|
-
const backend = config.backend || await detectBackend();
|
|
378
|
-
if (backend ===
|
|
379
|
-
const data = await proxyRequest(config,
|
|
444
|
+
const backend = config.backend || (await detectBackend());
|
|
445
|
+
if (backend === "proxy") {
|
|
446
|
+
const data = await proxyRequest(config, "/sign-authorization", { auth });
|
|
380
447
|
return data;
|
|
381
448
|
}
|
|
382
449
|
const privateKey = await _loadPrivateKeyInternal(config);
|
|
383
450
|
if (!privateKey)
|
|
384
|
-
throw new Error(
|
|
451
|
+
throw new Error("No wallet found. Run createWallet() first.");
|
|
385
452
|
const account = privateKeyToAccount(privateKey);
|
|
386
453
|
// EIP-7702 authorization signing using viem experimental
|
|
387
454
|
const chainId = auth.chainId ?? 1;
|
|
@@ -415,13 +482,13 @@ export async function signAuthorization(auth, config = {}) {
|
|
|
415
482
|
* Prefer signMessage() / signTransaction() when possible.
|
|
416
483
|
*/
|
|
417
484
|
export async function getWalletClient(rpcUrl, config = {}) {
|
|
418
|
-
const backend = config.backend || await detectBackend();
|
|
419
|
-
if (backend ===
|
|
420
|
-
throw new Error(
|
|
485
|
+
const backend = config.backend || (await detectBackend());
|
|
486
|
+
if (backend === "proxy") {
|
|
487
|
+
throw new Error("getWalletClient() is not supported via proxy. The private key cannot be serialized over HTTP. Use signMessage() or signTransaction() instead.");
|
|
421
488
|
}
|
|
422
489
|
const privateKey = await _loadPrivateKeyInternal(config);
|
|
423
490
|
if (!privateKey)
|
|
424
|
-
throw new Error(
|
|
491
|
+
throw new Error("No wallet found. Run createWallet() first.");
|
|
425
492
|
const account = privateKeyToAccount(privateKey);
|
|
426
493
|
return createWalletClient({
|
|
427
494
|
account,
|
|
@@ -433,20 +500,20 @@ export async function getWalletClient(rpcUrl, config = {}) {
|
|
|
433
500
|
* DESTRUCTIVE — the identity is lost if no backup exists.
|
|
434
501
|
*/
|
|
435
502
|
export async function deleteWallet(config = {}) {
|
|
436
|
-
const backend = config.backend || await detectBackend();
|
|
437
|
-
if (backend ===
|
|
438
|
-
throw new Error(
|
|
503
|
+
const backend = config.backend || (await detectBackend());
|
|
504
|
+
if (backend === "proxy") {
|
|
505
|
+
throw new Error("deleteWallet() is not supported via proxy. Delete the wallet on the proxy server directly.");
|
|
439
506
|
}
|
|
440
507
|
const keystorePath = config.keystorePath || process.env.KEYSTORE_PATH || DEFAULT_KEYSTORE_PATH;
|
|
441
508
|
switch (backend) {
|
|
442
|
-
case
|
|
509
|
+
case "encrypted-file":
|
|
443
510
|
if (fs.existsSync(keystorePath)) {
|
|
444
511
|
fs.unlinkSync(keystorePath);
|
|
445
512
|
return true;
|
|
446
513
|
}
|
|
447
514
|
return false;
|
|
448
|
-
case
|
|
449
|
-
console.warn(
|
|
515
|
+
case "env":
|
|
516
|
+
console.warn("Cannot delete env-based wallet. Unset AGENT_PRIVATE_KEY manually.");
|
|
450
517
|
return false;
|
|
451
518
|
}
|
|
452
519
|
}
|
|
@@ -454,19 +521,21 @@ export async function deleteWallet(config = {}) {
|
|
|
454
521
|
// Internal — loads the private key. NEVER exposed publicly.
|
|
455
522
|
// ---------------------------------------------------------------------------
|
|
456
523
|
async function _loadPrivateKeyInternal(config = {}) {
|
|
457
|
-
const backend = config.backend || await detectBackend();
|
|
524
|
+
const backend = config.backend || (await detectBackend());
|
|
458
525
|
const keystorePath = config.keystorePath || process.env.KEYSTORE_PATH || DEFAULT_KEYSTORE_PATH;
|
|
459
526
|
let privateKey = null;
|
|
460
527
|
switch (backend) {
|
|
461
|
-
case
|
|
462
|
-
const password = config.password ||
|
|
528
|
+
case "encrypted-file": {
|
|
529
|
+
const password = config.password ||
|
|
530
|
+
process.env.KEYSTORE_PASSWORD ||
|
|
531
|
+
deriveMachinePassword();
|
|
463
532
|
privateKey = await encryptedFileLoad(password, keystorePath);
|
|
464
533
|
break;
|
|
465
534
|
}
|
|
466
|
-
case
|
|
535
|
+
case "env": {
|
|
467
536
|
const envKey = process.env.AGENT_PRIVATE_KEY || null;
|
|
468
537
|
if (envKey) {
|
|
469
|
-
privateKey = (envKey.startsWith(
|
|
538
|
+
privateKey = (envKey.startsWith("0x") ? envKey : `0x${envKey}`);
|
|
470
539
|
}
|
|
471
540
|
break;
|
|
472
541
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@buildersgarden/siwa",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.5",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"exports": {
|
|
6
6
|
".": {
|
|
@@ -37,9 +37,18 @@
|
|
|
37
37
|
"files": [
|
|
38
38
|
"dist"
|
|
39
39
|
],
|
|
40
|
+
"repository": {
|
|
41
|
+
"type": "git",
|
|
42
|
+
"url": "https://github.com/builders-garden/siwa",
|
|
43
|
+
"directory": "packages/siwa"
|
|
44
|
+
},
|
|
40
45
|
"publishConfig": {
|
|
41
46
|
"access": "public"
|
|
42
47
|
},
|
|
48
|
+
"scripts": {
|
|
49
|
+
"build": "tsc",
|
|
50
|
+
"clean": "rm -rf dist"
|
|
51
|
+
},
|
|
43
52
|
"dependencies": {
|
|
44
53
|
"@noble/ciphers": "^0.5.0",
|
|
45
54
|
"@noble/hashes": "^1.4.0",
|
|
@@ -48,9 +57,5 @@
|
|
|
48
57
|
"devDependencies": {
|
|
49
58
|
"@types/node": "^25.2.1",
|
|
50
59
|
"typescript": "^5.5.0"
|
|
51
|
-
},
|
|
52
|
-
"scripts": {
|
|
53
|
-
"build": "tsc",
|
|
54
|
-
"clean": "rm -rf dist"
|
|
55
60
|
}
|
|
56
|
-
}
|
|
61
|
+
}
|
package/LICENSE
DELETED
|
@@ -1,21 +0,0 @@
|
|
|
1
|
-
MIT License
|
|
2
|
-
|
|
3
|
-
Copyright (c) 2026 Builders Garden SRL
|
|
4
|
-
|
|
5
|
-
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
-
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
-
in the Software without restriction, including without limitation the rights
|
|
8
|
-
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
-
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
-
furnished to do so, subject to the following conditions:
|
|
11
|
-
|
|
12
|
-
The above copyright notice and this permission notice shall be included in all
|
|
13
|
-
copies or substantial portions of the Software.
|
|
14
|
-
|
|
15
|
-
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
-
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
-
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
-
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
-
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
-
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
-
SOFTWARE.
|