@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.
- package/README.md +406 -5
- package/dist/account-add.d.ts +94 -0
- package/dist/account-add.d.ts.map +1 -0
- package/dist/agent-link.d.ts +15 -1
- package/dist/agent-link.d.ts.map +1 -1
- package/dist/agent-link.js +110 -44
- package/dist/agent-link.js.map +3 -3
- package/dist/agent-profile.d.ts +35 -6
- package/dist/agent-profile.d.ts.map +1 -1
- package/dist/allocation.d.ts +33 -0
- package/dist/allocation.d.ts.map +1 -0
- package/dist/api-key.js +4 -2
- package/dist/api-key.js.map +1 -1
- package/dist/audit.d.ts +1 -1
- package/dist/audit.d.ts.map +1 -1
- package/dist/backup.d.ts +186 -0
- package/dist/backup.d.ts.map +1 -0
- package/dist/backup.js +47542 -0
- package/dist/backup.js.map +6 -0
- package/dist/ceiling.d.ts +90 -0
- package/dist/ceiling.d.ts.map +1 -0
- package/dist/cloud-backup.d.ts +125 -0
- package/dist/cloud-backup.d.ts.map +1 -0
- package/dist/cloud-backup.js +48402 -0
- package/dist/cloud-backup.js.map +6 -0
- package/dist/config.d.ts +5 -0
- package/dist/config.d.ts.map +1 -1
- package/dist/device.d.ts +15 -0
- package/dist/device.d.ts.map +1 -0
- package/dist/distribute.d.ts +60 -0
- package/dist/distribute.d.ts.map +1 -0
- package/dist/index.d.ts +11 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +6538 -1571
- package/dist/index.js.map +3 -3
- package/dist/keystore.d.ts +24 -1
- package/dist/keystore.d.ts.map +1 -1
- package/dist/movement.d.ts +102 -0
- package/dist/movement.d.ts.map +1 -0
- package/dist/networks.d.ts +1 -0
- package/dist/networks.d.ts.map +1 -1
- package/dist/receipts.d.ts +24 -0
- package/dist/receipts.d.ts.map +1 -1
- package/dist/router-client.d.ts.map +1 -1
- package/dist/router-client.js +35918 -17192
- package/dist/router-client.js.map +3 -3
- package/dist/secret-store.d.ts +17 -5
- package/dist/secret-store.d.ts.map +1 -1
- package/dist/secrets.d.ts +1 -0
- package/dist/secrets.d.ts.map +1 -1
- package/dist/secrets.js +571 -24
- package/dist/secrets.js.map +3 -3
- package/dist/siblings.d.ts +41 -0
- package/dist/siblings.d.ts.map +1 -0
- package/dist/spend-policy.d.ts +12 -0
- package/dist/spend-policy.d.ts.map +1 -1
- package/dist/status.d.ts +73 -0
- package/dist/status.d.ts.map +1 -0
- package/dist/support-report.d.ts +1 -1
- package/dist/transfer.d.ts +62 -0
- package/dist/transfer.d.ts.map +1 -0
- package/dist/vault-key.d.ts +41 -0
- package/dist/vault-key.d.ts.map +1 -0
- package/dist/vault-migrate.d.ts +29 -0
- package/dist/vault-migrate.d.ts.map +1 -0
- package/dist/vault.d.ts +93 -0
- package/dist/vault.d.ts.map +1 -0
- package/dist/wallet-store.d.ts +116 -31
- package/dist/wallet-store.d.ts.map +1 -1
- package/dist/x402-pay.d.ts +5 -0
- package/dist/x402-pay.d.ts.map +1 -1
- 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:
|
|
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 ?
|
|
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
|
-
|
|
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
|
|
47003
|
-
const key = await deriveKey(passphrase, salt);
|
|
47031
|
+
const key = await deriveScryptKey(passphrase, salt);
|
|
47004
47032
|
try {
|
|
47005
|
-
const
|
|
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:
|
|
47011
|
-
authTag:
|
|
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
|
|
47066
|
+
const key = await deriveScryptKey(passphrase, salt);
|
|
47040
47067
|
let unlocked;
|
|
47041
47068
|
try {
|
|
47042
|
-
|
|
47043
|
-
|
|
47044
|
-
|
|
47045
|
-
|
|
47046
|
-
|
|
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
|