@anywalletkit/extension-kit 0.3.2 → 0.3.4

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/index.d.cts CHANGED
@@ -81,6 +81,8 @@ interface VaultEnvelopeV1 {
81
81
  dek: SealedBox;
82
82
  /** One box per key source (keyring source id), sealed under the DEK. AAD `forgewallet-vault/secret/<id>`. */
83
83
  secrets: Record<string, SealedBox>;
84
+ /** A fixed value sealed under the DEK (AAD `forgewallet-vault/dek-check`): proves a key is this vault's while it holds no secret. Absent in envelopes written before it existed. */
85
+ dekCheck?: SealedBox;
84
86
  createdAt: number;
85
87
  }
86
88
  interface VaultPasskey {
@@ -106,6 +108,8 @@ interface VaultEnvelopeV2 {
106
108
  };
107
109
  dek: SealedBox;
108
110
  secrets: Record<string, SealedBox>;
111
+ /** See {@link VaultEnvelopeV1.dekCheck}. */
112
+ dekCheck?: SealedBox;
109
113
  passkeys: VaultPasskey[];
110
114
  createdAt: number;
111
115
  /** When a v1 envelope was re-sealed as v2. */
@@ -237,9 +241,12 @@ declare class Vault {
237
241
  private _random;
238
242
  /** A fresh envelope (v2 by default, v1 with `kdf: 'pbkdf2'`) wrapping `dek` under `password`. */
239
243
  private _sealEnvelope;
244
+ /** The DEK check box: {@link DEK_CHECK} sealed under `dek`. */
245
+ private _sealDekCheck;
240
246
  /**
241
- * The caller's DEK must open this envelope's secrets — it is the key the password protects, not
242
- * any 32 bytes. An empty vault has nothing to prove it with, so it is refused rather than trusted.
247
+ * The caller's DEK must open this envelope's DEK check (or, in an envelope written before it, its
248
+ * first secret) — it is the key the password protects, not any 32 bytes. An old empty envelope has
249
+ * nothing to prove it with, so it is refused rather than trusted.
243
250
  */
244
251
  private _requireDek;
245
252
  private _derivePbkdf2;
@@ -1306,12 +1313,16 @@ interface SwapRecord {
1306
1313
  checks: number;
1307
1314
  }
1308
1315
  declare const ACTIVE_STATUSES: ReadonlySet<SwapRecordStatus>;
1309
- /** `awk1.<provider>.<intentId>[.<sourceTxHash>[.<claimTxHash>]]` — the 4th segment is on the id `claimSwap2` returns. */
1316
+ /**
1317
+ * `awk1.<provider>.<intentId>[.<sourceTxHash>[.<claimTxHash>[.ok]]]` — the 4th segment is on the id `claimSwap2`
1318
+ * returns, and `.ok` after it when the destination account saw the claim succeed (`destTxConfirmed`).
1319
+ */
1310
1320
  declare function parseSwapId(id: string): {
1311
1321
  provider: string;
1312
1322
  intentId: string;
1313
1323
  sourceTxHash?: string;
1314
1324
  destTxHash?: string;
1325
+ destTxConfirmed?: true;
1315
1326
  } | null;
1316
1327
  declare class SwapsStore {
1317
1328
  private readonly storage;
package/dist/index.d.ts CHANGED
@@ -81,6 +81,8 @@ interface VaultEnvelopeV1 {
81
81
  dek: SealedBox;
82
82
  /** One box per key source (keyring source id), sealed under the DEK. AAD `forgewallet-vault/secret/<id>`. */
83
83
  secrets: Record<string, SealedBox>;
84
+ /** A fixed value sealed under the DEK (AAD `forgewallet-vault/dek-check`): proves a key is this vault's while it holds no secret. Absent in envelopes written before it existed. */
85
+ dekCheck?: SealedBox;
84
86
  createdAt: number;
85
87
  }
86
88
  interface VaultPasskey {
@@ -106,6 +108,8 @@ interface VaultEnvelopeV2 {
106
108
  };
107
109
  dek: SealedBox;
108
110
  secrets: Record<string, SealedBox>;
111
+ /** See {@link VaultEnvelopeV1.dekCheck}. */
112
+ dekCheck?: SealedBox;
109
113
  passkeys: VaultPasskey[];
110
114
  createdAt: number;
111
115
  /** When a v1 envelope was re-sealed as v2. */
@@ -237,9 +241,12 @@ declare class Vault {
237
241
  private _random;
238
242
  /** A fresh envelope (v2 by default, v1 with `kdf: 'pbkdf2'`) wrapping `dek` under `password`. */
239
243
  private _sealEnvelope;
244
+ /** The DEK check box: {@link DEK_CHECK} sealed under `dek`. */
245
+ private _sealDekCheck;
240
246
  /**
241
- * The caller's DEK must open this envelope's secrets — it is the key the password protects, not
242
- * any 32 bytes. An empty vault has nothing to prove it with, so it is refused rather than trusted.
247
+ * The caller's DEK must open this envelope's DEK check (or, in an envelope written before it, its
248
+ * first secret) — it is the key the password protects, not any 32 bytes. An old empty envelope has
249
+ * nothing to prove it with, so it is refused rather than trusted.
243
250
  */
244
251
  private _requireDek;
245
252
  private _derivePbkdf2;
@@ -1306,12 +1313,16 @@ interface SwapRecord {
1306
1313
  checks: number;
1307
1314
  }
1308
1315
  declare const ACTIVE_STATUSES: ReadonlySet<SwapRecordStatus>;
1309
- /** `awk1.<provider>.<intentId>[.<sourceTxHash>[.<claimTxHash>]]` — the 4th segment is on the id `claimSwap2` returns. */
1316
+ /**
1317
+ * `awk1.<provider>.<intentId>[.<sourceTxHash>[.<claimTxHash>[.ok]]]` — the 4th segment is on the id `claimSwap2`
1318
+ * returns, and `.ok` after it when the destination account saw the claim succeed (`destTxConfirmed`).
1319
+ */
1310
1320
  declare function parseSwapId(id: string): {
1311
1321
  provider: string;
1312
1322
  intentId: string;
1313
1323
  sourceTxHash?: string;
1314
1324
  destTxHash?: string;
1325
+ destTxConfirmed?: true;
1315
1326
  } | null;
1316
1327
  declare class SwapsStore {
1317
1328
  private readonly storage;
package/dist/index.js CHANGED
@@ -72,6 +72,8 @@ var aadDekV1 = (kdf) => `forgewallet-vault/1/dek/${kdf.name}/${kdf.iterations}/$
72
72
  var aadDekV2 = (kdf) => `forgewallet-vault/2/dek/scrypt/${kdf.N}/${kdf.r}/${kdf.p}/${kdf.salt}`;
73
73
  var aadPasskey = (id) => `forgewallet-vault/2/passkey/${id}`;
74
74
  var aadSecret = (id) => `forgewallet-vault/secret/${id}`;
75
+ var AAD_DEK_CHECK = "forgewallet-vault/dek-check";
76
+ var DEK_CHECK = "forgewallet-vault/dek-check/1";
75
77
  var HKDF_INFO = "forgewallet-vault/2/passkey";
76
78
  var Vault = class {
77
79
  _storage;
@@ -285,7 +287,7 @@ var Vault = class {
285
287
  const key = await this._importDek(dek);
286
288
  return this._exclusive(async () => {
287
289
  const envelope = await this._load();
288
- if (Object.keys(envelope.secrets).length > 0) await this._requireDek(dek, envelope);
290
+ if (envelope.dekCheck !== void 0 || Object.keys(envelope.secrets).length > 0) await this._requireDek(dek, envelope);
289
291
  if (!(sourceId in envelope.secrets) && Object.keys(envelope.secrets).length >= VAULT_LIMITS.secrets) {
290
292
  throw new VaultError(`A vault holds at most ${VAULT_LIMITS.secrets} secrets.`, "invalid-input");
291
293
  }
@@ -338,19 +340,31 @@ var Vault = class {
338
340
  if (this._kdf === "pbkdf2") {
339
341
  const kdf2 = { name: "PBKDF2-SHA256", iterations: this._iterations, salt: toBase64(salt) };
340
342
  const kek2 = await this._derivePbkdf2(password, salt, this._iterations);
341
- return { v: 1, kdf: kdf2, dek: await this._seal(kek2, dek, aadDekV1(kdf2)), secrets, createdAt };
343
+ return { v: 1, kdf: kdf2, dek: await this._seal(kek2, dek, aadDekV1(kdf2)), secrets, dekCheck: await this._sealDekCheck(dek), createdAt };
342
344
  }
343
345
  const kdf = { name: "scrypt", ...this._scrypt, salt: toBase64(salt) };
344
346
  const kek = await this._deriveScrypt(password, salt, kdf);
345
- return { v: 2, kdf, dek: await this._seal(kek, dek, aadDekV2(kdf)), secrets, passkeys, createdAt };
347
+ return { v: 2, kdf, dek: await this._seal(kek, dek, aadDekV2(kdf)), secrets, dekCheck: await this._sealDekCheck(dek), passkeys, createdAt };
348
+ }
349
+ /** The DEK check box: {@link DEK_CHECK} sealed under `dek`. */
350
+ async _sealDekCheck(dek) {
351
+ return this._seal(await this._importDek(dek), new TextEncoder().encode(DEK_CHECK), AAD_DEK_CHECK);
346
352
  }
347
353
  /**
348
- * The caller's DEK must open this envelope's secrets — it is the key the password protects, not
349
- * any 32 bytes. An empty vault has nothing to prove it with, so it is refused rather than trusted.
354
+ * The caller's DEK must open this envelope's DEK check (or, in an envelope written before it, its
355
+ * first secret) — it is the key the password protects, not any 32 bytes. An old empty envelope has
356
+ * nothing to prove it with, so it is refused rather than trusted.
350
357
  */
351
358
  async _requireDek(dek, envelope) {
352
359
  const ids = Object.keys(envelope.secrets);
353
360
  const key = await this._importDek(dek);
361
+ if (envelope.dekCheck !== void 0) {
362
+ const check = await this._open(key, envelope.dekCheck, AAD_DEK_CHECK, "locked");
363
+ const ok = new TextDecoder().decode(check) === DEK_CHECK;
364
+ zeroize(check);
365
+ if (!ok) throw new VaultError("The vault key does not open this entry; lock and unlock again.", "locked");
366
+ return;
367
+ }
354
368
  if (ids.length === 0) throw new VaultError("Add a secret to the vault before a passkey: an empty vault cannot prove the key is its own.", "invalid-input");
355
369
  const probe = await this._open(key, envelope.secrets[ids[0]], aadSecret(ids[0]), "locked");
356
370
  zeroize(probe);
@@ -420,13 +434,13 @@ function isEnvelopeV1(x) {
420
434
  if (typeof x !== "object" || x === null) return false;
421
435
  const e = x;
422
436
  const kdf = e.kdf;
423
- return e.v === 1 && typeof kdf === "object" && kdf !== null && kdf.name === "PBKDF2-SHA256" && Number.isInteger(kdf.iterations) && typeof kdf.salt === "string" && isBox(e.dek) && typeof e.secrets === "object" && e.secrets !== null && Object.values(e.secrets).every(isBox);
437
+ return e.v === 1 && typeof kdf === "object" && kdf !== null && kdf.name === "PBKDF2-SHA256" && Number.isInteger(kdf.iterations) && typeof kdf.salt === "string" && isBox(e.dek) && typeof e.secrets === "object" && e.secrets !== null && Object.values(e.secrets).every(isBox) && (e.dekCheck === void 0 || isBox(e.dekCheck));
424
438
  }
425
439
  function isEnvelopeV2(x) {
426
440
  if (typeof x !== "object" || x === null) return false;
427
441
  const e = x;
428
442
  const kdf = e.kdf;
429
- return e.v === 2 && typeof kdf === "object" && kdf !== null && kdf.name === "scrypt" && Number.isInteger(kdf.N) && Number.isInteger(kdf.r) && Number.isInteger(kdf.p) && typeof kdf.salt === "string" && isBox(e.dek) && typeof e.secrets === "object" && e.secrets !== null && Object.values(e.secrets).every(isBox) && Array.isArray(e.passkeys) && e.passkeys.every((p) => typeof p === "object" && p !== null && typeof p.id === "string" && typeof p.label === "string" && isBox(p.dek));
443
+ return e.v === 2 && typeof kdf === "object" && kdf !== null && kdf.name === "scrypt" && Number.isInteger(kdf.N) && Number.isInteger(kdf.r) && Number.isInteger(kdf.p) && typeof kdf.salt === "string" && isBox(e.dek) && typeof e.secrets === "object" && e.secrets !== null && Object.values(e.secrets).every(isBox) && (e.dekCheck === void 0 || isBox(e.dekCheck)) && Array.isArray(e.passkeys) && e.passkeys.every((p) => typeof p === "object" && p !== null && typeof p.id === "string" && typeof p.label === "string" && isBox(p.dek));
430
444
  }
431
445
  function isBox(x) {
432
446
  return typeof x === "object" && x !== null && typeof x.iv === "string" && typeof x.data === "string";
@@ -441,7 +455,7 @@ function envelopeProblem(e) {
441
455
  if (secrets.length > VAULT_LIMITS.secrets) return `${secrets.length} secrets > ${VAULT_LIMITS.secrets}`;
442
456
  const passkeys = e.v === 2 ? e.passkeys : [];
443
457
  if (passkeys.length > VAULT_LIMITS.passkeys) return `${passkeys.length} passkeys > ${VAULT_LIMITS.passkeys}`;
444
- for (const box of [e.dek, ...secrets, ...passkeys.map((p) => p.dek)]) {
458
+ for (const box of [e.dek, ...e.dekCheck ? [e.dekCheck] : [], ...secrets, ...passkeys.map((p) => p.dek)]) {
445
459
  if (box.iv.length > VAULT_LIMITS.boxChars || box.data.length > VAULT_LIMITS.boxChars) return `a sealed box above ${VAULT_LIMITS.boxChars} characters`;
446
460
  }
447
461
  if (JSON.stringify(e).length > VAULT_LIMITS.envelopeChars) return `envelope above ${VAULT_LIMITS.envelopeChars} characters`;
@@ -1677,6 +1691,12 @@ function outgrown(shown, now) {
1677
1691
  if ("gasPrice" in now) return shown.gasPrice === void 0 || now.gasPrice > big(shown.gasPrice);
1678
1692
  return shown.maxFeePerGas === void 0 || now.base + now.tip > big(shown.maxFeePerGas);
1679
1693
  }
1694
+ var STALE_FEE_FACTOR = 4n;
1695
+ function overpriced(shown, fresh) {
1696
+ if (shown.gasPrice !== void 0 && fresh.gasPrice !== void 0) return big(shown.gasPrice) > STALE_FEE_FACTOR * big(fresh.gasPrice);
1697
+ if (shown.maxFeePerGas !== void 0 && fresh.maxFeePerGas !== void 0) return big(shown.maxFeePerGas) > STALE_FEE_FACTOR * big(fresh.maxFeePerGas);
1698
+ return false;
1699
+ }
1680
1700
  async function prepareTransaction(deps, network, from, tx) {
1681
1701
  const call = { from, to: tx.to, value: hex(BigInt(tx.value)), ...tx.data ? { data: tx.data } : {} };
1682
1702
  const [nonceHex, block, gasHex] = await Promise.all([
@@ -1722,6 +1742,8 @@ function createSignHandler(deps) {
1722
1742
  await deps.approvals.request({ origin, chain: network.caip2, kind: info.kind, payload: { ...payload, prepared: shown, repriced: true } });
1723
1743
  continue;
1724
1744
  }
1745
+ const fresh = withFees(network.chainId, nonce, big(shown.gasLimit), fees);
1746
+ if (overpriced(shown, fresh)) shown = fresh;
1725
1747
  const handle2 = await deps.signerFor(info.account, network);
1726
1748
  let raw;
1727
1749
  try {
@@ -1978,13 +2000,14 @@ var ACTIVE_STATUSES = /* @__PURE__ */ new Set(["pending", "action-required", "re
1978
2000
  function parseSwapId(id) {
1979
2001
  if (!id.startsWith(`${SWAP2_ID_PREFIX}.`)) return null;
1980
2002
  const parts = id.slice(SWAP2_ID_PREFIX.length + 1).split(".");
1981
- if (parts.length < 2 || parts.length > 4 || !parts[0] || !parts[1]) return null;
2003
+ if (parts.length < 2 || parts.length > 5 || !parts[0] || !parts[1]) return null;
2004
+ if (parts[4] !== void 0 && (parts[4] !== "ok" || !parts[3])) return null;
1982
2005
  const provider = decodeURIComponent(parts[0]);
1983
2006
  const intentId = decodeURIComponent(parts[1]);
1984
2007
  const sourceTxHash = parts[2] ? decodeURIComponent(parts[2]) : void 0;
1985
2008
  const destTxHash = parts[3] ? decodeURIComponent(parts[3]) : void 0;
1986
2009
  if (destTxHash && !sourceTxHash) return null;
1987
- return { provider, intentId, ...sourceTxHash ? { sourceTxHash } : {}, ...destTxHash ? { destTxHash } : {} };
2010
+ return { provider, intentId, ...sourceTxHash ? { sourceTxHash } : {}, ...destTxHash ? { destTxHash } : {}, ...parts[4] !== void 0 ? { destTxConfirmed: true } : {} };
1988
2011
  }
1989
2012
  var SwapsStore = class {
1990
2013
  constructor(storage) {