@vapi-network/core 0.5.0 → 0.7.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 (72) hide show
  1. package/README.md +406 -5
  2. package/dist/account-add.d.ts +94 -0
  3. package/dist/account-add.d.ts.map +1 -0
  4. package/dist/agent-link.d.ts +15 -1
  5. package/dist/agent-link.d.ts.map +1 -1
  6. package/dist/agent-link.js +110 -44
  7. package/dist/agent-link.js.map +3 -3
  8. package/dist/agent-profile.d.ts +35 -6
  9. package/dist/agent-profile.d.ts.map +1 -1
  10. package/dist/allocation.d.ts +33 -0
  11. package/dist/allocation.d.ts.map +1 -0
  12. package/dist/api-key.js +4 -2
  13. package/dist/api-key.js.map +1 -1
  14. package/dist/audit.d.ts +1 -1
  15. package/dist/audit.d.ts.map +1 -1
  16. package/dist/backup.d.ts +186 -0
  17. package/dist/backup.d.ts.map +1 -0
  18. package/dist/backup.js +47542 -0
  19. package/dist/backup.js.map +6 -0
  20. package/dist/ceiling.d.ts +90 -0
  21. package/dist/ceiling.d.ts.map +1 -0
  22. package/dist/cloud-backup.d.ts +125 -0
  23. package/dist/cloud-backup.d.ts.map +1 -0
  24. package/dist/cloud-backup.js +48402 -0
  25. package/dist/cloud-backup.js.map +6 -0
  26. package/dist/config.d.ts +5 -0
  27. package/dist/config.d.ts.map +1 -1
  28. package/dist/device.d.ts +15 -0
  29. package/dist/device.d.ts.map +1 -0
  30. package/dist/distribute.d.ts +60 -0
  31. package/dist/distribute.d.ts.map +1 -0
  32. package/dist/index.d.ts +11 -0
  33. package/dist/index.d.ts.map +1 -1
  34. package/dist/index.js +6538 -1571
  35. package/dist/index.js.map +3 -3
  36. package/dist/keystore.d.ts +24 -1
  37. package/dist/keystore.d.ts.map +1 -1
  38. package/dist/movement.d.ts +102 -0
  39. package/dist/movement.d.ts.map +1 -0
  40. package/dist/networks.d.ts +1 -0
  41. package/dist/networks.d.ts.map +1 -1
  42. package/dist/receipts.d.ts +24 -0
  43. package/dist/receipts.d.ts.map +1 -1
  44. package/dist/router-client.d.ts.map +1 -1
  45. package/dist/router-client.js +35918 -17192
  46. package/dist/router-client.js.map +3 -3
  47. package/dist/secret-store.d.ts +17 -5
  48. package/dist/secret-store.d.ts.map +1 -1
  49. package/dist/secrets.d.ts +1 -0
  50. package/dist/secrets.d.ts.map +1 -1
  51. package/dist/secrets.js +571 -24
  52. package/dist/secrets.js.map +3 -3
  53. package/dist/siblings.d.ts +41 -0
  54. package/dist/siblings.d.ts.map +1 -0
  55. package/dist/spend-policy.d.ts +12 -0
  56. package/dist/spend-policy.d.ts.map +1 -1
  57. package/dist/status.d.ts +73 -0
  58. package/dist/status.d.ts.map +1 -0
  59. package/dist/support-report.d.ts +1 -1
  60. package/dist/transfer.d.ts +62 -0
  61. package/dist/transfer.d.ts.map +1 -0
  62. package/dist/vault-key.d.ts +41 -0
  63. package/dist/vault-key.d.ts.map +1 -0
  64. package/dist/vault-migrate.d.ts +29 -0
  65. package/dist/vault-migrate.d.ts.map +1 -0
  66. package/dist/vault.d.ts +93 -0
  67. package/dist/vault.d.ts.map +1 -0
  68. package/dist/wallet-store.d.ts +116 -31
  69. package/dist/wallet-store.d.ts.map +1 -1
  70. package/dist/x402-pay.d.ts +5 -0
  71. package/dist/x402-pay.d.ts.map +1 -1
  72. package/package.json +9 -1
package/dist/secrets.js CHANGED
@@ -2212,7 +2212,7 @@ function weierstrass(curveDef) {
2212
2212
  function prepSig(msgHash, privateKey, opts = defaultSigOpts) {
2213
2213
  if (["recovered", "canonical"].some((k) => k in opts))
2214
2214
  throw new Error("sign() legacy options not supported");
2215
- const { hash: hash2, randomBytes: randomBytes3 } = CURVE;
2215
+ const { hash: hash2, randomBytes: randomBytes4 } = CURVE;
2216
2216
  let { lowS, prehash, extraEntropy: ent } = opts;
2217
2217
  if (lowS == null)
2218
2218
  lowS = true;
@@ -2224,7 +2224,7 @@ function weierstrass(curveDef) {
2224
2224
  const d = normPrivateKeyToScalar(privateKey);
2225
2225
  const seedArgs = [int2octets(d), int2octets(h1int)];
2226
2226
  if (ent != null && ent !== false) {
2227
- const e2 = ent === true ? randomBytes3(Fp.BYTES) : ent;
2227
+ const e2 = ent === true ? randomBytes4(Fp.BYTES) : ent;
2228
2228
  seedArgs.push(ensureBytes("extraEntropy", e2));
2229
2229
  }
2230
2230
  const seed = concatBytes2(...seedArgs);
@@ -46601,7 +46601,8 @@ var NETWORKS = {
46601
46601
  usdc: CANONICAL_X402_USDC_NETWORKS[BASE_MAINNET_CAIP2].usdc,
46602
46602
  gasToken: "ETH",
46603
46603
  rpcEnv: "BASE_RPC_URL",
46604
- publicRpcUrl: "https://mainnet.base.org"
46604
+ publicRpcUrl: "https://mainnet.base.org",
46605
+ explorerUrl: "https://basescan.org"
46605
46606
  },
46606
46607
  [ARC_MAINNET_CAIP2]: {
46607
46608
  family: "evm",
@@ -46696,7 +46697,8 @@ var configSchema = external_exports.object({
46696
46697
  }).optional()
46697
46698
  })
46698
46699
  ),
46699
- spendCaps: spendCapsSchema
46700
+ spendCaps: spendCapsSchema,
46701
+ device: external_exports.string().regex(/^[a-z0-9][a-z0-9-]{0,31}$/).optional()
46700
46702
  }).superRefine((config2, context) => {
46701
46703
  for (const [network, configured] of Object.entries(config2.networks)) {
46702
46704
  if (isSolanaNetwork(network)) {
@@ -46937,7 +46939,34 @@ var KeystoreError = class extends Error {
46937
46939
  this.name = "KeystoreError";
46938
46940
  }
46939
46941
  };
46940
- async function deriveKey(passphrase, salt) {
46942
+ function encryptWithKey(key, plaintext) {
46943
+ if (key.byteLength !== KEY_LENGTH) {
46944
+ throw new Error(`Expected a ${KEY_LENGTH}-byte AES-256-GCM key.`);
46945
+ }
46946
+ const nonce = randomBytes2(12);
46947
+ const cipher = createCipheriv("aes-256-gcm", key, nonce, {
46948
+ authTagLength: AUTH_TAG_LENGTH
46949
+ });
46950
+ const ciphertext = Buffer.concat([cipher.update(plaintext), cipher.final()]);
46951
+ return { nonce, ciphertext, tag: cipher.getAuthTag() };
46952
+ }
46953
+ function decryptWithKey(key, sealed) {
46954
+ if (key.byteLength !== KEY_LENGTH) {
46955
+ throw new Error(`Expected a ${KEY_LENGTH}-byte AES-256-GCM key.`);
46956
+ }
46957
+ if (sealed.nonce.byteLength !== 12) {
46958
+ throw new Error("Expected a 12-byte AES-GCM nonce.");
46959
+ }
46960
+ if (sealed.tag.byteLength !== AUTH_TAG_LENGTH) {
46961
+ throw new Error(`Expected a ${AUTH_TAG_LENGTH}-byte AES-GCM authentication tag.`);
46962
+ }
46963
+ const decipher = createDecipheriv("aes-256-gcm", key, sealed.nonce, {
46964
+ authTagLength: AUTH_TAG_LENGTH
46965
+ });
46966
+ decipher.setAuthTag(sealed.tag);
46967
+ return Buffer.concat([decipher.update(sealed.ciphertext), decipher.final()]);
46968
+ }
46969
+ async function deriveScryptKey(passphrase, salt) {
46941
46970
  return await new Promise((resolve, reject) => {
46942
46971
  scrypt(
46943
46972
  passphrase,
@@ -46999,16 +47028,14 @@ async function sealPlaintext(plaintext, passphrase) {
46999
47028
  throw new KeystoreError("Keystore passphrase cannot be empty.");
47000
47029
  }
47001
47030
  const salt = randomBytes2(32);
47002
- const iv = randomBytes2(12);
47003
- const key = await deriveKey(passphrase, salt);
47031
+ const key = await deriveScryptKey(passphrase, salt);
47004
47032
  try {
47005
- const cipher = createCipheriv("aes-256-gcm", key, iv, { authTagLength: AUTH_TAG_LENGTH });
47006
- const ciphertext = Buffer.concat([cipher.update(plaintext, "utf8"), cipher.final()]);
47033
+ const sealed = encryptWithKey(key, Buffer.from(plaintext, "utf8"));
47007
47034
  return {
47008
47035
  cipher: "aes-256-gcm",
47009
- ciphertext: ciphertext.toString("base64"),
47010
- iv: iv.toString("base64"),
47011
- authTag: cipher.getAuthTag().toString("base64"),
47036
+ ciphertext: sealed.ciphertext.toString("base64"),
47037
+ iv: sealed.nonce.toString("base64"),
47038
+ authTag: sealed.tag.toString("base64"),
47012
47039
  kdf: "scrypt",
47013
47040
  salt: salt.toString("base64"),
47014
47041
  kdfParams: {
@@ -47036,19 +47063,14 @@ async function decryptSecret(keystore, passphrase) {
47036
47063
  const iv = Buffer.from(parsed.crypto.iv, "base64");
47037
47064
  const authTag = Buffer.from(parsed.crypto.authTag, "base64");
47038
47065
  const ciphertext = Buffer.from(parsed.crypto.ciphertext, "base64");
47039
- const key = await deriveKey(passphrase, salt);
47066
+ const key = await deriveScryptKey(passphrase, salt);
47040
47067
  let unlocked;
47041
47068
  try {
47042
- if (authTag.length !== AUTH_TAG_LENGTH) {
47043
- throw new Error(`Expected a ${AUTH_TAG_LENGTH}-byte AES-GCM authentication tag.`);
47044
- }
47045
- const decipher = createDecipheriv("aes-256-gcm", key, iv, {
47046
- authTagLength: AUTH_TAG_LENGTH
47047
- });
47048
- decipher.setAuthTag(authTag);
47049
- const plaintext = Buffer.concat([decipher.update(ciphertext), decipher.final()]).toString(
47050
- "utf8"
47051
- );
47069
+ const plaintext = decryptWithKey(key, {
47070
+ nonce: iv,
47071
+ ciphertext,
47072
+ tag: authTag
47073
+ }).toString("utf8");
47052
47074
  unlocked = parsed.version === 3 ? keysFromEntropy(parsed, plaintext) : {
47053
47075
  keys: parsed.version === 1 ? { evm: `0x${plaintext}` } : encryptedKeysSchema.parse(JSON.parse(plaintext)).keys
47054
47076
  };
@@ -47133,6 +47155,15 @@ async function refuseExistingKeystore(path) {
47133
47155
  }
47134
47156
  }
47135
47157
  }
47158
+ function validatePrivateKey(value) {
47159
+ const normalized = value.trim().toLowerCase();
47160
+ if (!/^0x[0-9a-f]{64}$/u.test(normalized)) {
47161
+ throw new KeystoreError(
47162
+ "A private key is 0x followed by 64 hexadecimal characters. Check for a missing character."
47163
+ );
47164
+ }
47165
+ return normalized;
47166
+ }
47136
47167
  async function readStoredKeystore(path) {
47137
47168
  let raw;
47138
47169
  try {
@@ -47229,10 +47260,526 @@ async function writeKeystore(path, keystore, exclusive) {
47229
47260
  }
47230
47261
  await rename2(temporaryPath, path);
47231
47262
  }
47263
+
47264
+ // src/vault.ts
47265
+ import { randomBytes as randomBytes3 } from "node:crypto";
47266
+ import { mkdir as mkdir3, open as open2, readFile as readFile3, rename as rename3, rm, stat as stat3, unlink, writeFile as writeFile3 } from "node:fs/promises";
47267
+ import { dirname as dirname3 } from "node:path";
47268
+
47269
+ // src/wallet-name.ts
47270
+ var WALLET_NAME_PATTERN = /^[a-z0-9][a-z0-9-]{0,31}$/;
47271
+ var walletNameSchema = external_exports.string().regex(
47272
+ WALLET_NAME_PATTERN,
47273
+ "A wallet name is 1 to 32 characters of lowercase letters, digits and dashes, starting with a letter or digit."
47274
+ );
47275
+
47276
+ // src/vault.ts
47277
+ var VaultError = class extends Error {
47278
+ code;
47279
+ constructor(code, message, options) {
47280
+ super(message, options);
47281
+ this.name = "VaultError";
47282
+ this.code = code;
47283
+ }
47284
+ };
47285
+ var SEALED_FIELD_PATTERN = /^[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+$/u;
47286
+ var WRAPPED_KEY_PATTERN = /^[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+$/u;
47287
+ var ADDRESS_PATTERN = /^0x[0-9a-fA-F]{40}$/u;
47288
+ var VAULT_LOCK_TIMEOUT_MS = 2e3;
47289
+ var STALE_VAULT_LOCK_MS = 3e4;
47290
+ var sealedFieldSchema = external_exports.string().regex(SEALED_FIELD_PATTERN);
47291
+ var addressSchema = external_exports.string().regex(ADDRESS_PATTERN);
47292
+ var accountFields = {
47293
+ name: walletNameSchema,
47294
+ address: addressSchema,
47295
+ createdAt: external_exports.string()
47296
+ };
47297
+ var storedAccountSchema = external_exports.discriminatedUnion("kind", [
47298
+ external_exports.object({
47299
+ ...accountFields,
47300
+ kind: external_exports.literal("derived"),
47301
+ index: external_exports.number().int().nonnegative().refine(Number.isSafeInteger, "Derived account index must be a safe integer.")
47302
+ }).strict(),
47303
+ external_exports.object({
47304
+ ...accountFields,
47305
+ kind: external_exports.literal("imported"),
47306
+ key: sealedFieldSchema
47307
+ }).strict()
47308
+ ]);
47309
+ var vaultFileSchema = external_exports.object({
47310
+ version: external_exports.literal(2),
47311
+ kdf: external_exports.literal("device-key"),
47312
+ cipher: external_exports.literal("aes-256-gcm"),
47313
+ phrase: sealedFieldSchema,
47314
+ accounts: external_exports.array(storedAccountSchema),
47315
+ nextDerivedIndex: external_exports.number().int().nonnegative().refine(Number.isSafeInteger, "Next derived account index must be a safe integer.").optional(),
47316
+ protected: external_exports.boolean(),
47317
+ wrappedKey: external_exports.string().regex(WRAPPED_KEY_PATTERN).optional()
47318
+ }).strict();
47319
+ var DeviceVault = class {
47320
+ file;
47321
+ path;
47322
+ key;
47323
+ balanceOf;
47324
+ now;
47325
+ constructor(options, file2) {
47326
+ this.path = options.path;
47327
+ this.key = options.key;
47328
+ this.balanceOf = options.balanceOf;
47329
+ this.now = options.now ?? (() => /* @__PURE__ */ new Date());
47330
+ this.file = file2;
47331
+ }
47332
+ accounts() {
47333
+ return this.file.accounts.map(copyAccount);
47334
+ }
47335
+ async deriveAccount(name) {
47336
+ try {
47337
+ return await this.update(async (file2) => {
47338
+ assertAvailableName(file2, name);
47339
+ const index = nextDerivedIndex(file2);
47340
+ const plaintext = decryptField(this.key, file2.phrase);
47341
+ let seed;
47342
+ let address;
47343
+ try {
47344
+ seed = phraseToSeed(plaintext.toString("utf8"));
47345
+ address = privateKeyToAccount(
47346
+ deriveEvmPrivateKey(seed, `m/44'/60'/0'/0/${index}`)
47347
+ ).address;
47348
+ } finally {
47349
+ seed?.fill(0);
47350
+ plaintext.fill(0);
47351
+ }
47352
+ const account = {
47353
+ name,
47354
+ kind: "derived",
47355
+ index,
47356
+ address,
47357
+ createdAt: this.now().toISOString()
47358
+ };
47359
+ file2.accounts.push(account);
47360
+ file2.nextDerivedIndex = index + 1;
47361
+ return copyAccount(account);
47362
+ });
47363
+ } catch (error61) {
47364
+ throw asVaultError(error61, "corrupt", "Unable to derive a vault account.");
47365
+ }
47366
+ }
47367
+ async importAccount(name, privateKey) {
47368
+ try {
47369
+ return await this.update(async (file2) => {
47370
+ assertAvailableName(file2, name);
47371
+ let validated;
47372
+ let address;
47373
+ try {
47374
+ validated = validatePrivateKey(privateKey);
47375
+ address = privateKeyToAccount(validated).address;
47376
+ } catch (error61) {
47377
+ throw new VaultError("bad_key", "The imported private key is not valid.", {
47378
+ cause: error61
47379
+ });
47380
+ }
47381
+ const plaintext = Buffer.from(validated.slice(2), "hex");
47382
+ let account;
47383
+ try {
47384
+ account = {
47385
+ name,
47386
+ kind: "imported",
47387
+ address,
47388
+ key: sealField(this.key, plaintext),
47389
+ createdAt: this.now().toISOString()
47390
+ };
47391
+ } finally {
47392
+ plaintext.fill(0);
47393
+ }
47394
+ file2.accounts.push(account);
47395
+ return copyAccount(account);
47396
+ });
47397
+ } catch (error61) {
47398
+ throw asVaultError(error61, "corrupt", "Unable to import a vault account.");
47399
+ }
47400
+ }
47401
+ async removeAccount(name) {
47402
+ try {
47403
+ await this.update(async (file2) => {
47404
+ const account = file2.accounts.find((candidate) => candidate.name === name);
47405
+ if (!account) {
47406
+ throw new VaultError("not_found", "No vault account has that name.");
47407
+ }
47408
+ if (!this.balanceOf) {
47409
+ throw new VaultError(
47410
+ "has_balance",
47411
+ "The account balance could not be verified, so the key was not removed."
47412
+ );
47413
+ }
47414
+ if (await this.balanceOf(account.address) > 0n) {
47415
+ throw new VaultError(
47416
+ "has_balance",
47417
+ "This account still has a balance. Run vapi sweep before removing it."
47418
+ );
47419
+ }
47420
+ file2.nextDerivedIndex = nextDerivedIndex(file2);
47421
+ const currentIndex = file2.accounts.findIndex((candidate) => candidate.name === name);
47422
+ if (currentIndex === -1) {
47423
+ throw new VaultError("not_found", "No vault account has that name.");
47424
+ }
47425
+ file2.accounts.splice(currentIndex, 1);
47426
+ });
47427
+ } catch (error61) {
47428
+ throw asVaultError(error61, "corrupt", "Unable to remove the vault account.");
47429
+ }
47430
+ }
47431
+ async rename(from, to) {
47432
+ try {
47433
+ await this.update(async (file2) => {
47434
+ const account = file2.accounts.find((candidate) => candidate.name === from);
47435
+ if (!account) {
47436
+ throw new VaultError("not_found", "No vault account has that name.");
47437
+ }
47438
+ if (from === to) return;
47439
+ assertAccountName(to);
47440
+ if (file2.accounts.some((candidate) => candidate.name === to)) {
47441
+ throw new VaultError("name_taken", "A vault account already has that name.");
47442
+ }
47443
+ account.name = to;
47444
+ });
47445
+ } catch (error61) {
47446
+ throw asVaultError(error61, "corrupt", "Unable to rename the vault account.");
47447
+ }
47448
+ }
47449
+ async signer(name) {
47450
+ try {
47451
+ const latest = await readVaultFile(this.path, this.key);
47452
+ this.file = latest;
47453
+ const stored = latest.accounts.find((account2) => account2.name === name);
47454
+ if (!stored) {
47455
+ throw new VaultError("not_found", "No vault account has that name.");
47456
+ }
47457
+ let account;
47458
+ if (stored.kind === "derived") {
47459
+ const plaintext = decryptField(this.key, latest.phrase);
47460
+ let seed;
47461
+ try {
47462
+ seed = phraseToSeed(plaintext.toString("utf8"));
47463
+ account = privateKeyToAccount(
47464
+ deriveEvmPrivateKey(seed, `m/44'/60'/0'/0/${stored.index}`)
47465
+ );
47466
+ } finally {
47467
+ seed?.fill(0);
47468
+ plaintext.fill(0);
47469
+ }
47470
+ } else {
47471
+ const plaintext = decryptField(this.key, stored.key);
47472
+ try {
47473
+ if (plaintext.byteLength !== 32) {
47474
+ throw new VaultError("corrupt", "An imported vault key has the wrong length.");
47475
+ }
47476
+ const privateKey = `0x${plaintext.toString("hex")}`;
47477
+ account = privateKeyToAccount(privateKey);
47478
+ } finally {
47479
+ plaintext.fill(0);
47480
+ }
47481
+ }
47482
+ if (account.address.toLowerCase() !== stored.address.toLowerCase()) {
47483
+ throw new VaultError("corrupt", "A vault key does not match its stored address.");
47484
+ }
47485
+ return account;
47486
+ } catch (error61) {
47487
+ throw asVaultError(error61, "corrupt", "Unable to open the vault account signer.");
47488
+ }
47489
+ }
47490
+ async phrase() {
47491
+ const latest = await readVaultFile(this.path, this.key);
47492
+ this.file = latest;
47493
+ const plaintext = decryptField(this.key, latest.phrase);
47494
+ try {
47495
+ try {
47496
+ return validateRecoveryPhrase(plaintext.toString("utf8"));
47497
+ } catch (error61) {
47498
+ throw new VaultError("corrupt", "The vault recovery phrase is not valid.", {
47499
+ cause: error61
47500
+ });
47501
+ }
47502
+ } finally {
47503
+ plaintext.fill(0);
47504
+ }
47505
+ }
47506
+ async save() {
47507
+ try {
47508
+ await this.update(async () => void 0);
47509
+ } catch (error61) {
47510
+ throw asVaultError(error61, "corrupt", "Unable to save the vault.");
47511
+ }
47512
+ }
47513
+ resealSecrets(file2) {
47514
+ const phrase = decryptField(this.key, file2.phrase);
47515
+ try {
47516
+ const accounts = file2.accounts.map((account) => {
47517
+ if (account.kind === "derived") return { ...account };
47518
+ const privateKey = decryptField(this.key, account.key);
47519
+ try {
47520
+ if (privateKey.byteLength !== 32) {
47521
+ throw new VaultError("corrupt", "An imported vault key has the wrong length.");
47522
+ }
47523
+ return { ...account, key: sealField(this.key, privateKey) };
47524
+ } finally {
47525
+ privateKey.fill(0);
47526
+ }
47527
+ });
47528
+ return {
47529
+ ...file2,
47530
+ phrase: sealField(this.key, phrase),
47531
+ accounts
47532
+ };
47533
+ } finally {
47534
+ phrase.fill(0);
47535
+ }
47536
+ }
47537
+ async update(operation) {
47538
+ return await withVaultLock(this.path, async () => {
47539
+ const latest = await readVaultFile(this.path, this.key);
47540
+ const result = await operation(latest);
47541
+ const rewritten = this.resealSecrets(latest);
47542
+ await writeVaultFile(this.path, rewritten);
47543
+ this.file = rewritten;
47544
+ return result;
47545
+ });
47546
+ }
47547
+ };
47548
+ async function openVault(args) {
47549
+ assertVaultKey(args.key);
47550
+ return new DeviceVault(args, await readVaultFile(args.path, args.key));
47551
+ }
47552
+ async function exportVaultPhrase(args) {
47553
+ return await (await openVault(args)).phrase();
47554
+ }
47555
+ async function exportVaultAccountKey(args) {
47556
+ assertVaultKey(args.key);
47557
+ try {
47558
+ const file2 = await readVaultFile(args.path, args.key);
47559
+ const account = file2.accounts.find((candidate) => candidate.name === args.name);
47560
+ if (!account) {
47561
+ throw new VaultError("not_found", "No vault account has that name.");
47562
+ }
47563
+ let privateKey;
47564
+ if (account.kind === "derived") {
47565
+ const plaintext = decryptField(args.key, file2.phrase);
47566
+ let seed;
47567
+ try {
47568
+ seed = phraseToSeed(plaintext.toString("utf8"));
47569
+ privateKey = deriveEvmPrivateKey(seed, `m/44'/60'/0'/0/${account.index}`);
47570
+ } finally {
47571
+ seed?.fill(0);
47572
+ plaintext.fill(0);
47573
+ }
47574
+ } else {
47575
+ const plaintext = decryptField(args.key, account.key);
47576
+ try {
47577
+ if (plaintext.byteLength !== 32) {
47578
+ throw new VaultError("corrupt", "An imported vault key has the wrong length.");
47579
+ }
47580
+ privateKey = `0x${plaintext.toString("hex")}`;
47581
+ } finally {
47582
+ plaintext.fill(0);
47583
+ }
47584
+ }
47585
+ if (privateKeyToAccount(privateKey).address.toLowerCase() !== account.address.toLowerCase()) {
47586
+ throw new VaultError("corrupt", "A vault key does not match its stored address.");
47587
+ }
47588
+ return privateKey;
47589
+ } catch (error61) {
47590
+ throw asVaultError(error61, "corrupt", "Unable to export the vault account key.");
47591
+ }
47592
+ }
47593
+ function assertVaultKey(key) {
47594
+ if (key.byteLength !== 32) {
47595
+ throw new VaultError("bad_key", "A device vault key must contain exactly 32 bytes.");
47596
+ }
47597
+ }
47598
+ function assertAccountName(name) {
47599
+ const parsed = walletNameSchema.safeParse(name);
47600
+ if (!parsed.success) {
47601
+ throw new VaultError("name_taken", "The vault account name is not valid.", {
47602
+ cause: parsed.error
47603
+ });
47604
+ }
47605
+ }
47606
+ function assertAvailableName(file2, name) {
47607
+ assertAccountName(name);
47608
+ if (file2.accounts.some((account) => account.name === name)) {
47609
+ throw new VaultError("name_taken", "A vault account already has that name.");
47610
+ }
47611
+ }
47612
+ function nextDerivedIndex(file2) {
47613
+ let next = file2.nextDerivedIndex ?? 0;
47614
+ for (const account of file2.accounts) {
47615
+ if (account.kind === "derived") next = Math.max(next, account.index + 1);
47616
+ }
47617
+ return next;
47618
+ }
47619
+ function copyAccount(account) {
47620
+ if (account.kind === "derived") return { ...account };
47621
+ return {
47622
+ name: account.name,
47623
+ kind: account.kind,
47624
+ address: account.address,
47625
+ createdAt: account.createdAt
47626
+ };
47627
+ }
47628
+ function sealField(key, plaintext) {
47629
+ const { nonce, ciphertext, tag } = encryptWithKey(key, plaintext);
47630
+ return [nonce, ciphertext, tag].map((part) => part.toString("base64url")).join(".");
47631
+ }
47632
+ function openField(key, value) {
47633
+ const [nonce = "", ciphertext = "", tag = ""] = value.split(".");
47634
+ return decryptWithKey(key, {
47635
+ nonce: Buffer.from(nonce, "base64url"),
47636
+ ciphertext: Buffer.from(ciphertext, "base64url"),
47637
+ tag: Buffer.from(tag, "base64url")
47638
+ });
47639
+ }
47640
+ function decryptField(key, value) {
47641
+ try {
47642
+ return openField(key, value);
47643
+ } catch (error61) {
47644
+ throw new VaultError("bad_key", "Unable to decrypt the vault with this device key.", {
47645
+ cause: error61
47646
+ });
47647
+ }
47648
+ }
47649
+ function decryptPhraseForOpen(key, phrase) {
47650
+ try {
47651
+ return openField(key, phrase);
47652
+ } catch (error61) {
47653
+ throw new VaultError("bad_key", "Unable to decrypt the vault with this device key.", {
47654
+ cause: error61
47655
+ });
47656
+ }
47657
+ }
47658
+ function parseVaultFile(raw) {
47659
+ let value;
47660
+ try {
47661
+ value = JSON.parse(raw);
47662
+ } catch (error61) {
47663
+ throw new VaultError("corrupt", "The vault file is not valid JSON.", { cause: error61 });
47664
+ }
47665
+ const parsed = vaultFileSchema.safeParse(value);
47666
+ if (!parsed.success) {
47667
+ throw new VaultError("corrupt", "The vault file has an invalid structure.", {
47668
+ cause: parsed.error
47669
+ });
47670
+ }
47671
+ if (parsed.data.protected !== (parsed.data.wrappedKey !== void 0)) {
47672
+ throw new VaultError("corrupt", "The vault protection metadata is inconsistent.");
47673
+ }
47674
+ const names = /* @__PURE__ */ new Set();
47675
+ const indexes = /* @__PURE__ */ new Set();
47676
+ for (const account of parsed.data.accounts) {
47677
+ if (names.has(account.name)) {
47678
+ throw new VaultError("corrupt", "The vault file contains duplicate account names.");
47679
+ }
47680
+ names.add(account.name);
47681
+ if (account.kind === "derived") {
47682
+ if (indexes.has(account.index)) {
47683
+ throw new VaultError("corrupt", "The vault file contains duplicate derived indexes.");
47684
+ }
47685
+ indexes.add(account.index);
47686
+ }
47687
+ }
47688
+ return parsed.data;
47689
+ }
47690
+ async function readVaultFile(path, key) {
47691
+ const file2 = await readVaultFileUnlocked(path);
47692
+ const plaintext = decryptPhraseForOpen(key, file2.phrase);
47693
+ try {
47694
+ try {
47695
+ validateRecoveryPhrase(plaintext.toString("utf8"));
47696
+ } catch (error61) {
47697
+ throw new VaultError("corrupt", "The vault recovery phrase is not valid.", {
47698
+ cause: error61
47699
+ });
47700
+ }
47701
+ } finally {
47702
+ plaintext.fill(0);
47703
+ }
47704
+ return file2;
47705
+ }
47706
+ async function readVaultFileUnlocked(path) {
47707
+ let raw;
47708
+ try {
47709
+ raw = await readFile3(path, "utf8");
47710
+ } catch (error61) {
47711
+ if (isMissingFile2(error61)) {
47712
+ throw new VaultError("not_found", "No vault file was found at the requested path.", {
47713
+ cause: error61
47714
+ });
47715
+ }
47716
+ throw new VaultError("corrupt", "Unable to read the vault file.", { cause: error61 });
47717
+ }
47718
+ return parseVaultFile(raw);
47719
+ }
47720
+ async function writeVaultFile(path, file2) {
47721
+ await mkdir3(dirname3(path), { recursive: true, mode: 448 });
47722
+ const temporaryPath = `${path}.${process.pid}.${randomBytes3(6).toString("hex")}.tmp`;
47723
+ try {
47724
+ await writeFile3(temporaryPath, `${JSON.stringify(file2, null, 2)}
47725
+ `, {
47726
+ encoding: "utf8",
47727
+ mode: 384,
47728
+ flag: "wx"
47729
+ });
47730
+ await rename3(temporaryPath, path);
47731
+ } catch (error61) {
47732
+ await rm(temporaryPath, { force: true }).catch(() => void 0);
47733
+ throw error61;
47734
+ }
47735
+ }
47736
+ async function withVaultLock(path, operation) {
47737
+ await mkdir3(dirname3(path), { recursive: true, mode: 448 });
47738
+ const lockPath = `${path}.lock`;
47739
+ const deadline = Date.now() + VAULT_LOCK_TIMEOUT_MS;
47740
+ let handle;
47741
+ while (!handle) {
47742
+ try {
47743
+ handle = await open2(lockPath, "wx", 384);
47744
+ } catch (error61) {
47745
+ if (!isExistingFile(error61)) throw error61;
47746
+ await removeStaleVaultLock(lockPath);
47747
+ if (Date.now() >= deadline) {
47748
+ throw new VaultError("corrupt", "Timed out waiting for the vault lock.");
47749
+ }
47750
+ await new Promise((resolve) => setTimeout(resolve, 20));
47751
+ }
47752
+ }
47753
+ try {
47754
+ return await operation();
47755
+ } finally {
47756
+ await handle.close();
47757
+ await unlink(lockPath).catch(() => void 0);
47758
+ }
47759
+ }
47760
+ async function removeStaleVaultLock(path) {
47761
+ try {
47762
+ const metadata = await stat3(path);
47763
+ if (Date.now() - metadata.mtimeMs > STALE_VAULT_LOCK_MS) await unlink(path);
47764
+ } catch (error61) {
47765
+ if (!isMissingFile2(error61)) throw error61;
47766
+ }
47767
+ }
47768
+ function isMissingFile2(error61) {
47769
+ return typeof error61 === "object" && error61 !== null && "code" in error61 && Reflect.get(error61, "code") === "ENOENT";
47770
+ }
47771
+ function isExistingFile(error61) {
47772
+ return typeof error61 === "object" && error61 !== null && "code" in error61 && Reflect.get(error61, "code") === "EEXIST";
47773
+ }
47774
+ function asVaultError(error61, code, message) {
47775
+ return error61 instanceof VaultError ? error61 : new VaultError(code, message, { cause: error61 });
47776
+ }
47232
47777
  export {
47233
47778
  createKeystoreWithPhrase,
47234
47779
  decryptPrivateKey,
47235
47780
  exportKeystoreKeys,
47236
- exportRecoveryPhrase
47781
+ exportRecoveryPhrase,
47782
+ exportVaultAccountKey,
47783
+ exportVaultPhrase
47237
47784
  };
47238
47785
  //# sourceMappingURL=secrets.js.map