@vapi-network/core 0.7.0 → 0.9.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 (129) hide show
  1. package/README.md +14 -1
  2. package/dist/account-add.d.ts +8 -1
  3. package/dist/account-add.d.ts.map +1 -1
  4. package/dist/account-movement-lock.d.ts +10 -0
  5. package/dist/account-movement-lock.d.ts.map +1 -0
  6. package/dist/agent-link.d.ts +1 -0
  7. package/dist/agent-link.d.ts.map +1 -1
  8. package/dist/agent-link.js +27 -11
  9. package/dist/agent-link.js.map +1 -1
  10. package/dist/agent-profile.d.ts +18 -2
  11. package/dist/agent-profile.d.ts.map +1 -1
  12. package/dist/agent-run.d.ts +12 -0
  13. package/dist/agent-run.d.ts.map +1 -0
  14. package/dist/allocation.d.ts +35 -4
  15. package/dist/allocation.d.ts.map +1 -1
  16. package/dist/api-key.js +47 -0
  17. package/dist/api-key.js.map +1 -1
  18. package/dist/atomic-file.d.ts +23 -0
  19. package/dist/atomic-file.d.ts.map +1 -0
  20. package/dist/audit.d.ts +4 -1
  21. package/dist/audit.d.ts.map +1 -1
  22. package/dist/backup.d.ts +155 -32
  23. package/dist/backup.d.ts.map +1 -1
  24. package/dist/backup.js +2600 -139
  25. package/dist/backup.js.map +3 -3
  26. package/dist/call-contracts-wire.d.ts +12 -12
  27. package/dist/ceiling.d.ts +8 -0
  28. package/dist/ceiling.d.ts.map +1 -1
  29. package/dist/cloud-backup.d.ts +8 -10
  30. package/dist/cloud-backup.d.ts.map +1 -1
  31. package/dist/cloud-backup.js +2876 -381
  32. package/dist/cloud-backup.js.map +3 -3
  33. package/dist/config.d.ts +9 -0
  34. package/dist/config.d.ts.map +1 -1
  35. package/dist/distribute.d.ts +9 -1
  36. package/dist/distribute.d.ts.map +1 -1
  37. package/dist/docs.d.ts +40 -0
  38. package/dist/docs.d.ts.map +1 -0
  39. package/dist/funding-policy.d.ts +29 -0
  40. package/dist/funding-policy.d.ts.map +1 -0
  41. package/dist/index.d.ts +12 -2
  42. package/dist/index.d.ts.map +1 -1
  43. package/dist/index.js +5832 -1213
  44. package/dist/index.js.map +3 -3
  45. package/dist/marketplace-contracts.d.ts +14 -14
  46. package/dist/member-bundle.d.ts +110 -0
  47. package/dist/member-bundle.d.ts.map +1 -0
  48. package/dist/movement-open.d.ts +10 -0
  49. package/dist/movement-open.d.ts.map +1 -0
  50. package/dist/movement.d.ts +77 -7
  51. package/dist/movement.d.ts.map +1 -1
  52. package/dist/receipts.d.ts +9 -0
  53. package/dist/receipts.d.ts.map +1 -1
  54. package/dist/router-client.d.ts +9 -1
  55. package/dist/router-client.d.ts.map +1 -1
  56. package/dist/router-client.js +2272 -609
  57. package/dist/router-client.js.map +3 -3
  58. package/dist/run-budget.d.ts +17 -0
  59. package/dist/run-budget.d.ts.map +1 -0
  60. package/dist/secret-store.d.ts +6 -0
  61. package/dist/secret-store.d.ts.map +1 -1
  62. package/dist/secrets.d.ts +2 -1
  63. package/dist/secrets.d.ts.map +1 -1
  64. package/dist/secrets.js +912 -48
  65. package/dist/secrets.js.map +3 -3
  66. package/dist/spend-policy.d.ts +41 -7
  67. package/dist/spend-policy.d.ts.map +1 -1
  68. package/dist/support-report.d.ts +1 -1
  69. package/dist/swarm-capital.d.ts +147 -0
  70. package/dist/swarm-capital.d.ts.map +1 -0
  71. package/dist/swarm.d.ts +224 -0
  72. package/dist/swarm.d.ts.map +1 -0
  73. package/dist/tasks/actions.d.ts +152 -0
  74. package/dist/tasks/actions.d.ts.map +1 -0
  75. package/dist/tasks/canonical-json.d.ts +6 -0
  76. package/dist/tasks/canonical-json.d.ts.map +1 -0
  77. package/dist/tasks/chain-error.d.ts +6 -0
  78. package/dist/tasks/chain-error.d.ts.map +1 -0
  79. package/dist/tasks/chain-port.d.ts +88 -0
  80. package/dist/tasks/chain-port.d.ts.map +1 -0
  81. package/dist/tasks/chain-verification.d.ts +36 -0
  82. package/dist/tasks/chain-verification.d.ts.map +1 -0
  83. package/dist/tasks/chain.d.ts +22 -0
  84. package/dist/tasks/chain.d.ts.map +1 -0
  85. package/dist/tasks/client.d.ts +76 -0
  86. package/dist/tasks/client.d.ts.map +1 -0
  87. package/dist/tasks/delivery-manifest.d.ts +24 -0
  88. package/dist/tasks/delivery-manifest.d.ts.map +1 -0
  89. package/dist/tasks/dispute-fee.d.ts +4 -0
  90. package/dist/tasks/dispute-fee.d.ts.map +1 -0
  91. package/dist/tasks/index.d.ts +22 -0
  92. package/dist/tasks/index.d.ts.map +1 -0
  93. package/dist/tasks/money.d.ts +17 -0
  94. package/dist/tasks/money.d.ts.map +1 -0
  95. package/dist/tasks/operation-support.d.ts +127 -0
  96. package/dist/tasks/operation-support.d.ts.map +1 -0
  97. package/dist/tasks/operations.d.ts +722 -0
  98. package/dist/tasks/operations.d.ts.map +1 -0
  99. package/dist/tasks/pending-transactions.d.ts +52 -0
  100. package/dist/tasks/pending-transactions.d.ts.map +1 -0
  101. package/dist/tasks/plan-guard.d.ts +326 -0
  102. package/dist/tasks/plan-guard.d.ts.map +1 -0
  103. package/dist/tasks/rpc.d.ts +8 -0
  104. package/dist/tasks/rpc.d.ts.map +1 -0
  105. package/dist/tasks/scope-bindings.d.ts +20 -0
  106. package/dist/tasks/scope-bindings.d.ts.map +1 -0
  107. package/dist/tasks/scope-terms.d.ts +75 -0
  108. package/dist/tasks/scope-terms.d.ts.map +1 -0
  109. package/dist/tasks/trusted-deployments.d.ts +14 -0
  110. package/dist/tasks/trusted-deployments.d.ts.map +1 -0
  111. package/dist/tasks/types.d.ts +4439 -0
  112. package/dist/tasks/types.d.ts.map +1 -0
  113. package/dist/tasks/verbs.d.ts +4 -0
  114. package/dist/tasks/verbs.d.ts.map +1 -0
  115. package/dist/tasks.js +43906 -0
  116. package/dist/tasks.js.map +6 -0
  117. package/dist/transfer-journal.d.ts +11 -0
  118. package/dist/transfer-journal.d.ts.map +1 -0
  119. package/dist/transfer.d.ts +4 -1
  120. package/dist/transfer.d.ts.map +1 -1
  121. package/dist/vault.d.ts +11 -0
  122. package/dist/vault.d.ts.map +1 -1
  123. package/dist/wallet-store.d.ts +19 -8
  124. package/dist/wallet-store.d.ts.map +1 -1
  125. package/dist/x402-pay.d.ts +7 -0
  126. package/dist/x402-pay.d.ts.map +1 -1
  127. package/dist/x402.d.ts +3 -0
  128. package/dist/x402.d.ts.map +1 -1
  129. package/package.json +5 -1
@@ -4373,7 +4373,7 @@ function weierstrass(curveDef) {
4373
4373
  function prepSig(msgHash, privateKey, opts = defaultSigOpts) {
4374
4374
  if (["recovered", "canonical"].some((k) => k in opts))
4375
4375
  throw new Error("sign() legacy options not supported");
4376
- const { hash: hash4, randomBytes: randomBytes5 } = CURVE;
4376
+ const { hash: hash4, randomBytes: randomBytes9 } = CURVE;
4377
4377
  let { lowS, prehash, extraEntropy: ent } = opts;
4378
4378
  if (lowS == null)
4379
4379
  lowS = true;
@@ -4385,7 +4385,7 @@ function weierstrass(curveDef) {
4385
4385
  const d = normPrivateKeyToScalar(privateKey);
4386
4386
  const seedArgs = [int2octets(d), int2octets(h1int)];
4387
4387
  if (ent != null && ent !== false) {
4388
- const e3 = ent === true ? randomBytes5(Fp.BYTES) : ent;
4388
+ const e3 = ent === true ? randomBytes9(Fp.BYTES) : ent;
4389
4389
  seedArgs.push(ensureBytes("extraEntropy", e3));
4390
4390
  }
4391
4391
  const seed = concatBytes3(...seedArgs);
@@ -28522,8 +28522,8 @@ function date4(params) {
28522
28522
 
28523
28523
  // src/agent-link.ts
28524
28524
  import { createHash } from "node:crypto";
28525
- import { mkdir as mkdir2, open as open2, stat, unlink } from "node:fs/promises";
28526
- import { dirname as dirname2 } from "node:path";
28525
+ import { mkdir as mkdir4, open as open3, stat as stat3, unlink as unlink2 } from "node:fs/promises";
28526
+ import { dirname as dirname3 } from "node:path";
28527
28527
 
28528
28528
  // ../../node_modules/.pnpm/viem@2.56.1_typescript@6.0.3_zod@4.5.4/node_modules/viem/_esm/utils/signature/hashMessage.js
28529
28529
  init_keccak256();
@@ -30917,12 +30917,14 @@ function uid(length = 11) {
30917
30917
  }
30918
30918
 
30919
30919
  // src/audit.ts
30920
- import { appendFile, chmod, mkdir, readFile } from "node:fs/promises";
30921
- import { join as join2 } from "node:path";
30920
+ import { appendFile, chmod, mkdir as mkdir3, readFile as readFile3 } from "node:fs/promises";
30921
+ import { join as join3 } from "node:path";
30922
30922
 
30923
30923
  // src/config.ts
30924
+ import { constants } from "node:fs";
30925
+ import { copyFile, mkdir as mkdir2, readFile as readFile2, rename as rename2, stat as stat2, writeFile } from "node:fs/promises";
30924
30926
  import { homedir } from "node:os";
30925
- import { dirname, join } from "node:path";
30927
+ import { dirname as dirname2, join as join2 } from "node:path";
30926
30928
 
30927
30929
  // ../../node_modules/.pnpm/viem@2.56.1_typescript@6.0.3_zod@4.5.4/node_modules/viem/_esm/utils/abi/encodeEventTopics.js
30928
30930
  init_abi();
@@ -36625,6 +36627,225 @@ init_getAddress();
36625
36627
  init_isAddress();
36626
36628
  init_toHex();
36627
36629
 
36630
+ // src/atomic-file.ts
36631
+ import { randomBytes as randomBytes2 } from "node:crypto";
36632
+ import { link, mkdir, open as open2, readdir, readFile, rename, stat, unlink } from "node:fs/promises";
36633
+ import { basename, dirname, join } from "node:path";
36634
+ var DEFAULT_LOCK_TIMEOUT_MS = 2e3;
36635
+ var LOCK_RETRY_MS = 20;
36636
+ var LOCK_STALE_AFTER_MS = 12e4;
36637
+ var LOCK_HEARTBEAT_MS = 3e4;
36638
+ var atomicFileTestHooks;
36639
+ var FileLockedError = class extends Error {
36640
+ constructor(message = "Timed out waiting for the file lock.") {
36641
+ super(message);
36642
+ this.name = "FileLockedError";
36643
+ }
36644
+ };
36645
+ async function withFileLock(lockPath, fn, options = {}) {
36646
+ await mkdir(dirname(lockPath), { recursive: true, mode: 448 });
36647
+ const deadline = Date.now() + (options.timeoutMs ?? DEFAULT_LOCK_TIMEOUT_MS);
36648
+ const ownerRecord = {
36649
+ pid: process.pid,
36650
+ token: randomBytes2(16).toString("hex")
36651
+ };
36652
+ const owner = JSON.stringify(ownerRecord);
36653
+ const releasedOwner = JSON.stringify({ ...ownerRecord, released: true });
36654
+ let handle;
36655
+ while (handle === void 0) {
36656
+ await atomicFileTestHooks?.beforeLockAttempt?.(lockPath);
36657
+ try {
36658
+ const candidate = await open2(lockPath, "wx", 384);
36659
+ try {
36660
+ await candidate.writeFile(owner, "utf8");
36661
+ await candidate.sync();
36662
+ await atomicFileTestHooks?.afterLockCreate?.(lockPath);
36663
+ if (await lockTakeoverInProgress(lockPath)) {
36664
+ await candidate.close();
36665
+ await removeOwnedLock(lockPath, [owner]);
36666
+ await removeAbandonedLockTombs(lockPath);
36667
+ if (Date.now() >= deadline) {
36668
+ throw new FileLockedError(options.lockedMessage);
36669
+ }
36670
+ await new Promise((resolve) => setTimeout(resolve, LOCK_RETRY_MS));
36671
+ continue;
36672
+ }
36673
+ handle = candidate;
36674
+ } catch (error61) {
36675
+ await candidate.close().catch(() => void 0);
36676
+ await removeOwnedLock(lockPath, [owner]).catch(() => void 0);
36677
+ throw error61;
36678
+ }
36679
+ } catch (error61) {
36680
+ if (!hasCode(error61, "EEXIST")) throw error61;
36681
+ await removeStaleLock(lockPath);
36682
+ if (Date.now() >= deadline) {
36683
+ throw new FileLockedError(options.lockedMessage);
36684
+ }
36685
+ await new Promise((resolve) => setTimeout(resolve, LOCK_RETRY_MS));
36686
+ }
36687
+ }
36688
+ const ownedHandle = handle;
36689
+ const heartbeat = setInterval(() => {
36690
+ const time3 = /* @__PURE__ */ new Date();
36691
+ void ownedHandle.utimes(time3, time3).catch(() => void 0);
36692
+ }, LOCK_HEARTBEAT_MS);
36693
+ heartbeat.unref();
36694
+ try {
36695
+ return await fn();
36696
+ } finally {
36697
+ clearInterval(heartbeat);
36698
+ try {
36699
+ await markLockReleased(ownedHandle, releasedOwner);
36700
+ } finally {
36701
+ try {
36702
+ await ownedHandle.close();
36703
+ } finally {
36704
+ await removeOwnedLock(lockPath, [owner, releasedOwner]);
36705
+ }
36706
+ }
36707
+ }
36708
+ }
36709
+ async function removeStaleLock(path) {
36710
+ try {
36711
+ const owner = await readFile(path, "utf8");
36712
+ if (lockOwnerReleased(owner)) {
36713
+ await removeOwnedLock(path, [owner]);
36714
+ return;
36715
+ }
36716
+ const metadata = await stat(path);
36717
+ if (Date.now() - metadata.mtimeMs <= LOCK_STALE_AFTER_MS) return;
36718
+ const pid = lockOwnerPid(owner);
36719
+ if (pid !== void 0 && processIsAlive(pid)) return;
36720
+ await atomicFileTestHooks?.beforeStaleLockBreak?.(path);
36721
+ await removeOwnedLock(path, [owner]);
36722
+ } catch (error61) {
36723
+ if (!hasCode(error61, "ENOENT")) throw error61;
36724
+ }
36725
+ }
36726
+ async function removeOwnedLock(path, owners) {
36727
+ const tombPath = `${path}.stale.${randomBytes2(16).toString("hex")}`;
36728
+ try {
36729
+ await rename(path, tombPath);
36730
+ } catch (error61) {
36731
+ if (hasCode(error61, "ENOENT")) {
36732
+ await removeOwnedTombs(path, owners);
36733
+ const currentOwner = await readFile(path, "utf8").catch(() => void 0);
36734
+ if (currentOwner !== void 0 && owners.includes(currentOwner)) {
36735
+ await removeOwnedLock(path, owners);
36736
+ }
36737
+ return;
36738
+ }
36739
+ throw error61;
36740
+ }
36741
+ let pendingError;
36742
+ try {
36743
+ const renamedOwner = await readFile(tombPath, "utf8");
36744
+ if (!owners.includes(renamedOwner) && !lockOwnerReleased(renamedOwner)) {
36745
+ await atomicFileTestHooks?.afterMismatchedLockRename?.(path);
36746
+ const currentOwner = await readFile(tombPath, "utf8");
36747
+ if (!lockOwnerReleased(currentOwner)) await restoreRenamedLock(path, tombPath);
36748
+ await atomicFileTestHooks?.afterMismatchedLockRestore?.(path);
36749
+ }
36750
+ } catch (error61) {
36751
+ pendingError = error61;
36752
+ }
36753
+ try {
36754
+ await unlink(tombPath);
36755
+ } catch (error61) {
36756
+ if (!hasCode(error61, "ENOENT") && pendingError === void 0) {
36757
+ pendingError = error61;
36758
+ }
36759
+ }
36760
+ if (pendingError !== void 0) throw pendingError;
36761
+ }
36762
+ async function lockTakeoverInProgress(path) {
36763
+ const prefix = `${basename(path)}.stale.`;
36764
+ return (await readdir(dirname(path))).some((entry) => entry.startsWith(prefix));
36765
+ }
36766
+ async function removeAbandonedLockTombs(path) {
36767
+ const directory = dirname(path);
36768
+ const prefix = `${basename(path)}.stale.`;
36769
+ for (const entry of await readdir(directory)) {
36770
+ if (!entry.startsWith(prefix)) continue;
36771
+ const tombPath = join(directory, entry);
36772
+ const [metadata, owner] = await Promise.all([
36773
+ stat(tombPath).catch(() => void 0),
36774
+ readFile(tombPath, "utf8").catch(() => void 0)
36775
+ ]);
36776
+ if (metadata === void 0 || owner === void 0) continue;
36777
+ const pid = lockOwnerPid(owner);
36778
+ const abandoned = lockOwnerReleased(owner) || Date.now() - metadata.mtimeMs > LOCK_STALE_AFTER_MS && (pid === void 0 || !processIsAlive(pid));
36779
+ if (!abandoned) continue;
36780
+ await unlink(tombPath).catch((error61) => {
36781
+ if (!hasCode(error61, "ENOENT")) throw error61;
36782
+ });
36783
+ }
36784
+ }
36785
+ async function removeOwnedTombs(path, owners) {
36786
+ const directory = dirname(path);
36787
+ const prefix = `${basename(path)}.stale.`;
36788
+ for (const entry of await readdir(directory)) {
36789
+ if (!entry.startsWith(prefix)) continue;
36790
+ const tombPath = join(directory, entry);
36791
+ const owner = await readFile(tombPath, "utf8").catch(() => void 0);
36792
+ if (owner === void 0 || !owners.includes(owner)) continue;
36793
+ await unlink(tombPath).catch((error61) => {
36794
+ if (!hasCode(error61, "ENOENT")) throw error61;
36795
+ });
36796
+ }
36797
+ }
36798
+ async function restoreRenamedLock(path, tombPath) {
36799
+ while (true) {
36800
+ try {
36801
+ await link(tombPath, path);
36802
+ return;
36803
+ } catch (error61) {
36804
+ if (hasCode(error61, "ENOENT")) return;
36805
+ if (!hasCode(error61, "EEXIST")) throw error61;
36806
+ await new Promise((resolve) => setTimeout(resolve, LOCK_RETRY_MS));
36807
+ }
36808
+ }
36809
+ }
36810
+ async function markLockReleased(handle, owner) {
36811
+ const contents = Buffer.from(owner, "utf8");
36812
+ await handle.write(contents, 0, contents.length, 0);
36813
+ await handle.truncate(contents.length);
36814
+ await handle.sync();
36815
+ }
36816
+ function lockOwnerPid(owner) {
36817
+ try {
36818
+ const value = JSON.parse(owner);
36819
+ if (typeof value !== "object" || value === null) return void 0;
36820
+ const pid = Reflect.get(value, "pid");
36821
+ return typeof pid === "number" && Number.isSafeInteger(pid) && pid > 0 ? pid : void 0;
36822
+ } catch {
36823
+ return void 0;
36824
+ }
36825
+ }
36826
+ function lockOwnerReleased(owner) {
36827
+ try {
36828
+ const value = JSON.parse(owner);
36829
+ return typeof value === "object" && value !== null && Reflect.get(value, "released") === true;
36830
+ } catch {
36831
+ return false;
36832
+ }
36833
+ }
36834
+ function processIsAlive(pid) {
36835
+ if (atomicFileTestHooks?.isProcessAlive !== void 0) {
36836
+ return atomicFileTestHooks.isProcessAlive(pid);
36837
+ }
36838
+ try {
36839
+ process.kill(pid, 0);
36840
+ return true;
36841
+ } catch (error61) {
36842
+ return hasCode(error61, "EPERM");
36843
+ }
36844
+ }
36845
+ function hasCode(error61, code) {
36846
+ return error61 instanceof Error && "code" in error61 && error61.code === code;
36847
+ }
36848
+
36628
36849
  // src/x402-networks.ts
36629
36850
  var BASE_MAINNET_CAIP2 = "eip155:8453";
36630
36851
  var ARC_MAINNET_CAIP2 = "eip155:5042";
@@ -37044,6 +37265,12 @@ function createNetworkHttpTransport(rpcUrl, options = {}) {
37044
37265
  var DEFAULT_REGISTRY_URL = "https://api.vapinetwork.ai";
37045
37266
  var DEFAULT_DISCOVERY_URL = `${DEFAULT_REGISTRY_URL}/api/call/services`;
37046
37267
  var DEFAULT_MARKETPLACE_DISCOVERY_URL = `${DEFAULT_REGISTRY_URL}/api/call/discovery`;
37268
+ var DEFAULT_REGISTRY_FALLBACKS = [
37269
+ {
37270
+ discoveryUrl: DEFAULT_DISCOVERY_URL,
37271
+ marketplaceDiscoveryUrl: DEFAULT_MARKETPLACE_DISCOVERY_URL
37272
+ }
37273
+ ];
37047
37274
  var DEFAULT_SPEND_CAPS = {
37048
37275
  perCallAtomic: "100000",
37049
37276
  perDayAtomic: "1000000"
@@ -37063,6 +37290,14 @@ var configSchema = external_exports.object({
37063
37290
  })
37064
37291
  ).optional(),
37065
37292
  allowPrivateNetwork: external_exports.boolean().default(false).optional(),
37293
+ tasksEscrowDurationOverrides: external_exports.record(
37294
+ external_exports.string().regex(/^\d+$/),
37295
+ external_exports.object({
37296
+ workDurationSeconds: external_exports.number().int().positive().max(Number.MAX_SAFE_INTEGER).optional(),
37297
+ reviewWindowSeconds: external_exports.number().int().positive().max(Number.MAX_SAFE_INTEGER).optional()
37298
+ }).strict()
37299
+ ).optional(),
37300
+ tasksEscrowFactoryOverrides: external_exports.record(external_exports.string().regex(/^\d+$/), external_exports.string()).optional(),
37066
37301
  networks: external_exports.record(
37067
37302
  external_exports.string().refine((network) => isSupportedPaymentNetwork(network), {
37068
37303
  message: "Expected a supported eip155:<chainId> or Solana network identifier."
@@ -37081,6 +37316,17 @@ var configSchema = external_exports.object({
37081
37316
  spendCaps: spendCapsSchema,
37082
37317
  device: external_exports.string().regex(/^[a-z0-9][a-z0-9-]{0,31}$/).optional()
37083
37318
  }).superRefine((config2, context) => {
37319
+ for (const [chainId, factory] of Object.entries(config2.tasksEscrowFactoryOverrides ?? {})) {
37320
+ try {
37321
+ getAddress(factory);
37322
+ } catch {
37323
+ context.addIssue({
37324
+ code: "custom",
37325
+ path: ["tasksEscrowFactoryOverrides", chainId],
37326
+ message: "Expected a valid EVM escrow factory address."
37327
+ });
37328
+ }
37329
+ }
37084
37330
  for (const [network, configured] of Object.entries(config2.networks)) {
37085
37331
  if (isSolanaNetwork(network)) {
37086
37332
  if (configured.usdc !== NETWORKS[SOLANA_MAINNET_CAIP2].usdc) {
@@ -37110,31 +37356,307 @@ var configSchema = external_exports.object({
37110
37356
  }
37111
37357
  }
37112
37358
  });
37113
- function getVapiPaths(directory = process.env.VAPI_HOME?.trim() || join(homedir(), ".vapi")) {
37359
+ function getVapiPaths(directory = process.env.VAPI_HOME?.trim() || join2(homedir(), ".vapi")) {
37114
37360
  return {
37115
37361
  directory,
37116
- agentsDir: join(directory, "agents"),
37117
- config: join(directory, "config.json"),
37118
- keystore: join(directory, "keystore.json"),
37119
- receipts: join(directory, "receipts.jsonl"),
37120
- searches: join(directory, "searches.jsonl"),
37121
- ledger: join(directory, "spend-ledger.json")
37362
+ agentsDir: join2(directory, "agents"),
37363
+ config: join2(directory, "config.json"),
37364
+ keystore: join2(directory, "keystore.json"),
37365
+ receipts: join2(directory, "receipts.jsonl"),
37366
+ searches: join2(directory, "searches.jsonl"),
37367
+ ledger: join2(directory, "spend-ledger.json")
37122
37368
  };
37123
37369
  }
37124
37370
  var getAgentCashPaths = getVapiPaths;
37371
+ async function migrateLegacyVapiHome(options = {}) {
37372
+ const target = getVapiPaths(options.targetDirectory);
37373
+ const legacyDirectory = options.legacyDirectory ?? join2(homedir(), ".vapi", "agent-cash");
37374
+ if (legacyDirectory === target.directory || !await pathExists(legacyDirectory)) return [];
37375
+ await mkdir2(target.directory, { recursive: true, mode: 448 });
37376
+ const copied = [];
37377
+ for (const filename of [
37378
+ "config.json",
37379
+ "keystore.json",
37380
+ "receipts.jsonl",
37381
+ "searches.jsonl",
37382
+ "spend-ledger.json"
37383
+ ]) {
37384
+ const source = join2(legacyDirectory, filename);
37385
+ const destination = join2(target.directory, filename);
37386
+ try {
37387
+ const copy = async () => await copyFile(source, destination, constants.COPYFILE_EXCL);
37388
+ if (filename === "receipts.jsonl") {
37389
+ await withFileLock(`${destination}.lock`, copy, {
37390
+ lockedMessage: "The receipt journal is busy. Wait for the other vapi command to finish, then retry."
37391
+ });
37392
+ } else {
37393
+ await copy();
37394
+ }
37395
+ copied.push(destination);
37396
+ } catch (error61) {
37397
+ if (isMissingFile(error61) || isAlreadyExists(error61)) continue;
37398
+ throw error61;
37399
+ }
37400
+ }
37401
+ if (copied.length > 0) {
37402
+ const message = `Migrated legacy vAPI wallet files from ${legacyDirectory} to ${target.directory}; originals were kept.`;
37403
+ (options.notice ?? ((value) => process.stderr.write(`${value}
37404
+ `)))(message);
37405
+ }
37406
+ return copied;
37407
+ }
37408
+ var LEGACY_REGISTRY_HOSTS = /* @__PURE__ */ new Map([
37409
+ ["console.vapinetwork.ai", "api.vapinetwork.ai"],
37410
+ ["console-staging.vapinetwork.ai", "api-staging.vapinetwork.ai"]
37411
+ ]);
37412
+ var LEGACY_REGISTRY_PATHS = /* @__PURE__ */ new Map([
37413
+ ["/api/network/services", "/api/call/services"],
37414
+ ["/api/marketplace/discovery", "/api/call/discovery"]
37415
+ ]);
37416
+ var REGISTRY_DOMAIN = "vapinetwork.ai";
37417
+ function rewriteLegacyRegistryUrl(value) {
37418
+ let url2;
37419
+ try {
37420
+ url2 = new URL(value);
37421
+ } catch {
37422
+ return value;
37423
+ }
37424
+ let rewritten = false;
37425
+ const host = LEGACY_REGISTRY_HOSTS.get(url2.hostname.toLowerCase());
37426
+ if (host) {
37427
+ url2.hostname = host;
37428
+ rewritten = true;
37429
+ }
37430
+ const hostname3 = url2.hostname.toLowerCase();
37431
+ if (hostname3 === REGISTRY_DOMAIN || hostname3.endsWith(`.${REGISTRY_DOMAIN}`)) {
37432
+ const path = LEGACY_REGISTRY_PATHS.get(url2.pathname.replace(/\/+$/, ""));
37433
+ if (path) {
37434
+ url2.pathname = path;
37435
+ rewritten = true;
37436
+ }
37437
+ }
37438
+ return rewritten ? url2.href : value;
37439
+ }
37440
+ function rewriteLegacyRegistryUrls(config2) {
37441
+ const changes = [];
37442
+ const rewrite = (field, from15) => {
37443
+ const to = rewriteLegacyRegistryUrl(from15);
37444
+ if (to !== from15) changes.push({ field, from: from15, to });
37445
+ return to;
37446
+ };
37447
+ const next = {
37448
+ ...config2,
37449
+ discoveryUrl: rewrite("discoveryUrl", config2.discoveryUrl),
37450
+ marketplaceDiscoveryUrl: rewrite("marketplaceDiscoveryUrl", config2.marketplaceDiscoveryUrl),
37451
+ ...config2.registryFallbacks ? {
37452
+ registryFallbacks: config2.registryFallbacks.map((fallback, index2) => ({
37453
+ discoveryUrl: rewrite(
37454
+ `registryFallbacks[${index2}].discoveryUrl`,
37455
+ fallback.discoveryUrl
37456
+ ),
37457
+ marketplaceDiscoveryUrl: rewrite(
37458
+ `registryFallbacks[${index2}].marketplaceDiscoveryUrl`,
37459
+ fallback.marketplaceDiscoveryUrl
37460
+ )
37461
+ }))
37462
+ } : {}
37463
+ };
37464
+ return changes.length === 0 ? { config: config2, changes } : { config: next, changes };
37465
+ }
37466
+ function formatLegacyRegistryRewrites(changes) {
37467
+ return [
37468
+ "Rewrote retired vAPI registry URLs in the local config:",
37469
+ ...changes.map((change) => ` ${change.field}: ${change.from} -> ${change.to}`)
37470
+ ].join("\n");
37471
+ }
37472
+ function getDefaultConfig(source = process.env, options = {}) {
37473
+ const registry2 = registryEndpoints(source.VAPI_REGISTRY_URL?.trim() || DEFAULT_REGISTRY_URL);
37474
+ const requested = new Set(options.networks ?? ["base"]);
37475
+ const networks = {};
37476
+ const tasksEscrowFactoryOverrides = tasksFactoryOverridesFromEnv(source);
37477
+ if (requested.has("base") || requested.has(BASE_MAINNET_CAIP2)) {
37478
+ networks[BASE_MAINNET_CAIP2] = {
37479
+ rpcUrl: source.BASE_RPC_URL?.trim() || NETWORKS[BASE_MAINNET_CAIP2].publicRpcUrl,
37480
+ usdc: NETWORKS[BASE_MAINNET_CAIP2].usdc
37481
+ };
37482
+ }
37483
+ const arcMainnetRpcUrl = source.ARC_RPC_URL?.trim();
37484
+ if (arcMainnetRpcUrl || requested.has("arc") || requested.has(ARC_MAINNET_CAIP2)) {
37485
+ networks[ARC_MAINNET_CAIP2] = {
37486
+ rpcUrl: arcMainnetRpcUrl || NETWORKS[ARC_MAINNET_CAIP2].publicRpcUrl,
37487
+ usdc: NETWORKS[ARC_MAINNET_CAIP2].usdc
37488
+ };
37489
+ }
37490
+ const arcTestnetRpcUrl = source.ARC_TESTNET_RPC_URL?.trim();
37491
+ if (arcTestnetRpcUrl || requested.has("arc-testnet") || requested.has(ARC_TESTNET_CAIP2)) {
37492
+ if (!arcTestnetRpcUrl) {
37493
+ throw new Error(
37494
+ "Arc testnet RPC is required. Set ARC_TESTNET_RPC_URL before enabling Arc testnet."
37495
+ );
37496
+ }
37497
+ networks[ARC_TESTNET_CAIP2] = {
37498
+ rpcUrl: arcTestnetRpcUrl,
37499
+ usdc: NETWORKS[ARC_TESTNET_CAIP2].usdc
37500
+ };
37501
+ }
37502
+ const solanaRpcUrl = source.SOLANA_RPC_URL?.trim();
37503
+ if (solanaRpcUrl || requested.has("solana") || requested.has(SOLANA_MAINNET_CAIP2)) {
37504
+ networks[SOLANA_MAINNET_CAIP2] = {
37505
+ rpcUrl: solanaRpcUrl || NETWORKS[SOLANA_MAINNET_CAIP2].publicRpcUrl,
37506
+ usdc: NETWORKS[SOLANA_MAINNET_CAIP2].usdc
37507
+ };
37508
+ }
37509
+ return {
37510
+ discoveryUrl: source.VAPI_DISCOVERY_URL?.trim() || registry2.discoveryUrl,
37511
+ marketplaceDiscoveryUrl: source.VAPI_MARKETPLACE_DISCOVERY_URL?.trim() || registry2.marketplaceDiscoveryUrl,
37512
+ registryFallbacks: DEFAULT_REGISTRY_FALLBACKS.map((fallback) => ({ ...fallback })),
37513
+ allowPrivateNetwork: false,
37514
+ tasksEscrowFactoryOverrides,
37515
+ tasksEscrowDurationOverrides: tasksDurationOverridesFromEnv(source),
37516
+ networks,
37517
+ spendCaps: { ...DEFAULT_SPEND_CAPS }
37518
+ };
37519
+ }
37520
+ async function loadConfig(path = getVapiPaths().config, source = process.env, options = {}) {
37521
+ if (!process.env.VAPI_HOME?.trim() && path === getVapiPaths(join2(homedir(), ".vapi")).config) {
37522
+ await migrateLegacyVapiHome({ targetDirectory: join2(homedir(), ".vapi") });
37523
+ }
37524
+ let raw;
37525
+ try {
37526
+ raw = await readFile2(path, "utf8");
37527
+ } catch (error61) {
37528
+ if (isMissingFile(error61)) return getDefaultConfig(source);
37529
+ throw error61;
37530
+ }
37531
+ const parsed = configSchema.parse(JSON.parse(raw));
37532
+ parsed.registryFallbacks ??= DEFAULT_REGISTRY_FALLBACKS.map((fallback) => ({ ...fallback }));
37533
+ parsed.tasksEscrowFactoryOverrides ??= {};
37534
+ const migrated = rewriteLegacyRegistryUrls(parsed);
37535
+ const config2 = migrated.config;
37536
+ config2.tasksEscrowFactoryOverrides ??= {};
37537
+ if (migrated.changes.length > 0 && options.notice) {
37538
+ options.notice(
37539
+ `${formatLegacyRegistryRewrites(migrated.changes)}
37540
+ Run vapi init to write the new URLs to ${path}.`
37541
+ );
37542
+ }
37543
+ const baseRpcUrl = source.BASE_RPC_URL?.trim();
37544
+ const arcMainnetRpcUrl = source.ARC_RPC_URL?.trim();
37545
+ const arcTestnetRpcUrl = source.ARC_TESTNET_RPC_URL?.trim();
37546
+ const solanaRpcUrl = source.SOLANA_RPC_URL?.trim();
37547
+ const discoveryUrl = source.VAPI_DISCOVERY_URL?.trim();
37548
+ const marketplaceDiscoveryUrl = source.VAPI_MARKETPLACE_DISCOVERY_URL?.trim();
37549
+ const registryUrl = source.VAPI_REGISTRY_URL?.trim();
37550
+ Object.assign(config2.tasksEscrowFactoryOverrides, tasksFactoryOverridesFromEnv(source));
37551
+ config2.tasksEscrowDurationOverrides ??= {};
37552
+ for (const [chainId, overrides] of Object.entries(tasksDurationOverridesFromEnv(source))) {
37553
+ config2.tasksEscrowDurationOverrides[chainId] = {
37554
+ ...config2.tasksEscrowDurationOverrides[chainId],
37555
+ ...overrides
37556
+ };
37557
+ }
37558
+ if (config2.networks[BASE_MAINNET_CAIP2] && baseRpcUrl) {
37559
+ config2.networks[BASE_MAINNET_CAIP2].rpcUrl = baseRpcUrl;
37560
+ }
37561
+ if (arcMainnetRpcUrl) {
37562
+ const existing = config2.networks[ARC_MAINNET_CAIP2];
37563
+ config2.networks[ARC_MAINNET_CAIP2] = {
37564
+ ...existing ?? { usdc: NETWORKS[ARC_MAINNET_CAIP2].usdc },
37565
+ rpcUrl: arcMainnetRpcUrl
37566
+ };
37567
+ }
37568
+ if (arcTestnetRpcUrl) {
37569
+ const existing = config2.networks[ARC_TESTNET_CAIP2];
37570
+ config2.networks[ARC_TESTNET_CAIP2] = {
37571
+ ...existing ?? { usdc: NETWORKS[ARC_TESTNET_CAIP2].usdc },
37572
+ rpcUrl: arcTestnetRpcUrl
37573
+ };
37574
+ }
37575
+ if (solanaRpcUrl) {
37576
+ config2.networks[SOLANA_MAINNET_CAIP2] = {
37577
+ rpcUrl: solanaRpcUrl,
37578
+ usdc: config2.networks[SOLANA_MAINNET_CAIP2]?.usdc ?? NETWORKS[SOLANA_MAINNET_CAIP2].usdc
37579
+ };
37580
+ }
37581
+ if (registryUrl) {
37582
+ const registry2 = registryEndpoints(registryUrl);
37583
+ config2.discoveryUrl = registry2.discoveryUrl;
37584
+ config2.marketplaceDiscoveryUrl = registry2.marketplaceDiscoveryUrl;
37585
+ }
37586
+ if (discoveryUrl) config2.discoveryUrl = discoveryUrl;
37587
+ if (marketplaceDiscoveryUrl) config2.marketplaceDiscoveryUrl = marketplaceDiscoveryUrl;
37588
+ for (const [network, configured] of Object.entries(config2.networks)) {
37589
+ configured.rpcUrl = configured.rpcUrl.trim();
37590
+ configured.usdc = configured.usdc.trim();
37591
+ if (isSolanaNetwork(network) && configured.usdc !== NETWORKS[SOLANA_MAINNET_CAIP2].usdc) {
37592
+ throw new Error(`Network ${network} must use canonical Solana mainnet USDC.`);
37593
+ }
37594
+ if (!configured.rpcUrl) delete config2.networks[network];
37595
+ }
37596
+ return config2;
37597
+ }
37598
+ function tasksDurationOverridesFromEnv(source) {
37599
+ const result = {};
37600
+ for (const [name, raw] of Object.entries(source)) {
37601
+ const match = /^VAPI_TASKS_(WORK_DURATION|REVIEW_WINDOW)_SECONDS_(\d+)$/.exec(name);
37602
+ const value = raw?.trim();
37603
+ if (!match || !value) continue;
37604
+ const seconds = Number(value);
37605
+ if (!/^\d+$/.test(value) || !Number.isSafeInteger(seconds) || seconds <= 0)
37606
+ throw new Error(`Invalid trusted Tasks duration ${name}.`);
37607
+ const entry = result[match[2]] ??= {};
37608
+ if (match[1] === "WORK_DURATION") entry.workDurationSeconds = seconds;
37609
+ else entry.reviewWindowSeconds = seconds;
37610
+ }
37611
+ return result;
37612
+ }
37613
+ function tasksFactoryOverridesFromEnv(source) {
37614
+ const overrides = {};
37615
+ for (const [name, raw] of Object.entries(source)) {
37616
+ const match = /^VAPI_TASKS_ESCROW_FACTORY_(\d+)$/.exec(name);
37617
+ const value = raw?.trim();
37618
+ if (!match || !value) continue;
37619
+ overrides[match[1]] = getAddress(value);
37620
+ }
37621
+ return overrides;
37622
+ }
37623
+ function registryEndpoints(baseUrl) {
37624
+ const base = new URL(baseUrl);
37625
+ base.search = "";
37626
+ base.hash = "";
37627
+ const prefix = base.pathname === "/" ? "" : base.pathname.replace(/\/+$/, "");
37628
+ const discovery = new URL(base);
37629
+ discovery.pathname = `${prefix}/api/call/services`;
37630
+ const marketplace = new URL(base);
37631
+ marketplace.pathname = `${prefix}/api/call/discovery`;
37632
+ return { discoveryUrl: discovery.href, marketplaceDiscoveryUrl: marketplace.href };
37633
+ }
37634
+ async function pathExists(path) {
37635
+ try {
37636
+ await stat2(path);
37637
+ return true;
37638
+ } catch (error61) {
37639
+ if (isMissingFile(error61)) return false;
37640
+ throw error61;
37641
+ }
37642
+ }
37125
37643
  function isMissingFile(error61) {
37126
37644
  return error61 instanceof Error && "code" in error61 && error61.code === "ENOENT";
37127
37645
  }
37646
+ function isAlreadyExists(error61) {
37647
+ return error61 instanceof Error && "code" in error61 && error61.code === "EEXIST";
37648
+ }
37128
37649
 
37129
37650
  // src/audit.ts
37130
37651
  var AUDIT_FILE = "audit.log";
37131
37652
  function auditLogPath(home) {
37132
- return join2(home, AUDIT_FILE);
37653
+ return join3(home, AUDIT_FILE);
37133
37654
  }
37134
37655
  async function appendAudit(home, record2, options = {}) {
37135
37656
  const entry = {
37136
37657
  time: (options.now?.() ?? /* @__PURE__ */ new Date()).toISOString(),
37137
37658
  event: record2.event,
37659
+ ...record2.run === void 0 ? {} : { run: record2.run },
37138
37660
  ...record2.wallet === void 0 ? {} : { wallet: record2.wallet },
37139
37661
  ...record2.owner === void 0 ? {} : { owner: record2.owner },
37140
37662
  tty: record2.tty,
@@ -37142,15 +37664,19 @@ async function appendAudit(home, record2, options = {}) {
37142
37664
  ...record2.detail === void 0 ? {} : { detail: record2.detail }
37143
37665
  };
37144
37666
  const path = auditLogPath(home);
37145
- await mkdir(home, { recursive: true, mode: 448 });
37667
+ await mkdir3(home, { recursive: true, mode: 448 });
37146
37668
  await appendFile(path, `${JSON.stringify(entry)}
37147
37669
  `, { encoding: "utf8", mode: 384 });
37148
37670
  await chmod(path, 384);
37149
37671
  return entry;
37150
37672
  }
37151
37673
 
37674
+ // src/device.ts
37675
+ var DEVICE_NAME_PATTERN = /^[a-z0-9][a-z0-9-]{0,31}$/;
37676
+
37152
37677
  // src/agent-link.ts
37153
37678
  var AGENT_LINK_REVOKED_MESSAGE = "This agent's link was revoked or has expired. Run vapi login to link it again.";
37679
+ var REMOTE_AGENT_TOKEN_EXPIRED_MESSAGE = "The Railway run's short-lived vAPI access token expired. Start a new Railway run from the owner machine.";
37154
37680
  var AgentLinkError = class extends Error {
37155
37681
  constructor(code, message) {
37156
37682
  super(message);
@@ -37160,7 +37686,12 @@ var AgentLinkError = class extends Error {
37160
37686
  code;
37161
37687
  };
37162
37688
  var DEFAULT_AGENT_SCOPES = ["mcp:call", "router.use"];
37163
- var ALLOWED_AGENT_SCOPES = /* @__PURE__ */ new Set([...DEFAULT_AGENT_SCOPES, "call.publish"]);
37689
+ var ALLOWED_AGENT_SCOPES = /* @__PURE__ */ new Set([
37690
+ ...DEFAULT_AGENT_SCOPES,
37691
+ "call.publish",
37692
+ "tasks:read",
37693
+ "tasks:write"
37694
+ ]);
37164
37695
  var publicFetch = createPublicFetch({ allowPrivateNetwork: false });
37165
37696
  var agentSecretAccounts = (wallet) => ({
37166
37697
  tokens: `vapi.agent.${wallet}.tokens`,
@@ -37184,8 +37715,8 @@ async function agentFetch(args, url2, init) {
37184
37715
  }
37185
37716
  async function resolveAgentAccessToken(args, options) {
37186
37717
  await args.wallets.reload();
37187
- const link = args.wallets.entry(args.wallet)?.link;
37188
- if (link === void 0 || !args.secrets.available) throw notLinkedError();
37718
+ const link2 = args.wallets.entry(args.wallet)?.link;
37719
+ if (link2 === void 0 || !args.secrets.available) throw notLinkedError();
37189
37720
  const accounts = agentSecretAccounts(args.wallet);
37190
37721
  const tokens = parseTokens(await safeGetSecret(args.secrets, accounts.tokens));
37191
37722
  if (tokens === void 0) throw notLinkedError();
@@ -37193,7 +37724,7 @@ async function resolveAgentAccessToken(args, options) {
37193
37724
  if (options.rejectedAccessToken === void 0 && tokens.expiresAt - 6e4 > now()) {
37194
37725
  return tokens.accessToken;
37195
37726
  }
37196
- return await withAgentCredentialLock(args.wallets, link.clientId, async () => {
37727
+ return await withAgentCredentialLock(args.wallets, link2.clientId, async () => {
37197
37728
  await args.wallets.reload();
37198
37729
  const currentLink = args.wallets.entry(args.wallet)?.link;
37199
37730
  if (currentLink === void 0) throw notLinkedError();
@@ -37206,6 +37737,9 @@ async function resolveAgentAccessToken(args, options) {
37206
37737
  if (options.rejectedAccessToken === void 0 && current.expiresAt - 6e4 > now()) {
37207
37738
  return current.accessToken;
37208
37739
  }
37740
+ if (current.refreshToken === void 0) {
37741
+ throw new AgentLinkError("not_linked", REMOTE_AGENT_TOKEN_EXPIRED_MESSAGE);
37742
+ }
37209
37743
  const response = await safeFetch(
37210
37744
  args.fetchImpl ?? publicFetch,
37211
37745
  apiEndpoint(currentLink.apiBase, "/oauth/token"),
@@ -37239,12 +37773,12 @@ async function resolveAgentAccessToken(args, options) {
37239
37773
  async function withAgentCredentialLock(wallets, clientId, operation) {
37240
37774
  const digest = createHash("sha256").update(clientId).digest("hex").slice(0, 24);
37241
37775
  const lockPath = `${wallets.registryPath}.${digest}.agent-link.lock`;
37242
- await mkdir2(dirname2(lockPath), { recursive: true, mode: 448 });
37776
+ await mkdir4(dirname3(lockPath), { recursive: true, mode: 448 });
37243
37777
  const deadline = Date.now() + 3e4;
37244
37778
  let handle;
37245
37779
  while (!handle) {
37246
37780
  try {
37247
- handle = await open2(lockPath, "wx", 384);
37781
+ handle = await open3(lockPath, "wx", 384);
37248
37782
  } catch (error61) {
37249
37783
  if (!(error61 instanceof Error && "code" in error61 && error61.code === "EEXIST")) throw error61;
37250
37784
  await removeStaleCredentialLock(lockPath);
@@ -37258,13 +37792,13 @@ async function withAgentCredentialLock(wallets, clientId, operation) {
37258
37792
  return await operation();
37259
37793
  } finally {
37260
37794
  await handle.close();
37261
- await unlink(lockPath).catch(() => void 0);
37795
+ await unlink2(lockPath).catch(() => void 0);
37262
37796
  }
37263
37797
  }
37264
37798
  async function removeStaleCredentialLock(path) {
37265
37799
  try {
37266
- const metadata = await stat(path);
37267
- if (Date.now() - metadata.mtimeMs > 5 * 6e4) await unlink(path);
37800
+ const metadata = await stat3(path);
37801
+ if (Date.now() - metadata.mtimeMs > 5 * 6e4) await unlink2(path);
37268
37802
  } catch {
37269
37803
  }
37270
37804
  }
@@ -37293,12 +37827,18 @@ function parseTokens(value) {
37293
37827
  if (!isRecord(parsed)) return void 0;
37294
37828
  const accessToken = stringField(parsed, "accessToken");
37295
37829
  const refreshToken = stringField(parsed, "refreshToken");
37830
+ const refreshable = parsed.refreshable;
37296
37831
  const expiresAt = numberField(parsed, "expiresAt");
37297
37832
  const scopes = parsed.scopes;
37298
- if (accessToken === void 0 || refreshToken === void 0 || expiresAt === void 0 || !Array.isArray(scopes) || scopes.some((scope) => typeof scope !== "string")) {
37833
+ if (accessToken === void 0 || expiresAt === void 0 || !Array.isArray(scopes) || scopes.some((scope) => typeof scope !== "string") || (refreshToken === void 0 ? refreshable !== false : refreshable === false)) {
37299
37834
  return void 0;
37300
37835
  }
37301
- return { accessToken, refreshToken, expiresAt, scopes };
37836
+ return {
37837
+ accessToken,
37838
+ ...refreshToken === void 0 ? {} : { refreshToken },
37839
+ expiresAt,
37840
+ scopes
37841
+ };
37302
37842
  }
37303
37843
  function withBearer(init, accessToken) {
37304
37844
  const headers = new Headers(init?.headers);
@@ -37364,8 +37904,92 @@ function splitScopes(scope) {
37364
37904
  }
37365
37905
 
37366
37906
  // src/ceiling.ts
37367
- import { randomBytes as randomBytes4 } from "node:crypto";
37368
- import { open as open7, readFile as readFile5, stat as stat5, unlink as unlink5 } from "node:fs/promises";
37907
+ import { randomBytes as randomBytes7 } from "node:crypto";
37908
+
37909
+ // src/account-movement-lock.ts
37910
+ import { randomBytes as randomBytes3 } from "node:crypto";
37911
+ import { mkdir as mkdir5, open as open4, readFile as readFile4, stat as stat4, unlink as unlink3 } from "node:fs/promises";
37912
+ import { join as join4 } from "node:path";
37913
+ var AccountMovementLockedError = class extends Error {
37914
+ constructor(account) {
37915
+ super(`Another capital movement is already running for account ${account}.`);
37916
+ this.account = account;
37917
+ this.name = "AccountMovementLockedError";
37918
+ }
37919
+ account;
37920
+ code = "account_movement_locked";
37921
+ };
37922
+ async function withAccountMovementLock(home, account, timeoutMs, operation, signal) {
37923
+ const directory = join4(home, "movements");
37924
+ await mkdir5(directory, { recursive: true, mode: 448 });
37925
+ const path = join4(directory, `.${account}.lock`);
37926
+ const deadline = Date.now() + (timeoutMs ?? 2e3);
37927
+ const owner = JSON.stringify({ pid: process.pid, token: randomBytes3(16).toString("hex") });
37928
+ let handle;
37929
+ while (handle === void 0) {
37930
+ signal?.throwIfAborted();
37931
+ try {
37932
+ handle = await open4(path, "wx", 384);
37933
+ await handle.writeFile(owner, "utf8");
37934
+ await handle.sync();
37935
+ } catch (error61) {
37936
+ if (!(error61 instanceof Error && "code" in error61 && error61.code === "EEXIST")) throw error61;
37937
+ await removeStaleLock2(path);
37938
+ if (Date.now() >= deadline) throw new AccountMovementLockedError(account);
37939
+ await new Promise((resolve) => setTimeout(resolve, 20));
37940
+ }
37941
+ }
37942
+ const ownedHandle = handle;
37943
+ const heartbeat = setInterval(() => {
37944
+ const time3 = /* @__PURE__ */ new Date();
37945
+ void ownedHandle.utimes(time3, time3).catch(() => void 0);
37946
+ }, 3e4);
37947
+ heartbeat.unref();
37948
+ try {
37949
+ return await operation();
37950
+ } finally {
37951
+ clearInterval(heartbeat);
37952
+ await ownedHandle.close();
37953
+ await unlinkOwnedLock(path, owner);
37954
+ }
37955
+ }
37956
+ async function removeStaleLock2(path) {
37957
+ try {
37958
+ const metadata = await stat4(path);
37959
+ if (Date.now() - metadata.mtimeMs <= 12e4) return;
37960
+ const owner = await readFile4(path, "utf8").catch(() => "");
37961
+ const pid = lockOwnerPid2(owner);
37962
+ if (pid !== void 0 && processIsAlive2(pid)) return;
37963
+ await unlinkOwnedLock(path, owner);
37964
+ } catch (error61) {
37965
+ if (!isMissingFile(error61)) throw error61;
37966
+ }
37967
+ }
37968
+ async function unlinkOwnedLock(path, owner) {
37969
+ try {
37970
+ if (await readFile4(path, "utf8") === owner) await unlink3(path);
37971
+ } catch (error61) {
37972
+ if (!isMissingFile(error61)) throw error61;
37973
+ }
37974
+ }
37975
+ function lockOwnerPid2(owner) {
37976
+ try {
37977
+ const value = JSON.parse(owner);
37978
+ if (typeof value !== "object" || value === null) return void 0;
37979
+ const pid = Reflect.get(value, "pid");
37980
+ return typeof pid === "number" && Number.isSafeInteger(pid) && pid > 0 ? pid : void 0;
37981
+ } catch {
37982
+ return void 0;
37983
+ }
37984
+ }
37985
+ function processIsAlive2(pid) {
37986
+ try {
37987
+ process.kill(pid, 0);
37988
+ return true;
37989
+ } catch (error61) {
37990
+ return error61 instanceof Error && "code" in error61 && error61.code === "EPERM";
37991
+ }
37992
+ }
37369
37993
 
37370
37994
  // src/authorization-state.ts
37371
37995
  var EIP3009_AUTHORIZATION_STATE_ABI = [
@@ -37429,9 +38053,47 @@ function missingAuthorization(receipt) {
37429
38053
  return `Receipt ${receipt.id} records no signed payment authorization, so nothing from that call can settle.`;
37430
38054
  }
37431
38055
 
37432
- // src/receipts.ts
37433
- import { mkdir as mkdir3, open as open3, readFile as readFile2, rename, writeFile } from "node:fs/promises";
37434
- import { dirname as dirname3 } from "node:path";
38056
+ // src/movement.ts
38057
+ import { randomBytes as randomBytes6 } from "node:crypto";
38058
+ import { chmod as chmod3, mkdir as mkdir10, open as open9, readFile as readFile9, readdir as readdir3, rename as rename6, unlink as unlink7 } from "node:fs/promises";
38059
+ import { join as join5 } from "node:path";
38060
+
38061
+ // src/allocation.ts
38062
+ var AllocationError = class extends Error {
38063
+ constructor(code, message) {
38064
+ super(message);
38065
+ this.code = code;
38066
+ this.name = "AllocationError";
38067
+ }
38068
+ code;
38069
+ };
38070
+ function parseUsdCents(value) {
38071
+ const raw = String(value);
38072
+ if (!/^\d+(?:\.\d{1,2})?$/.test(raw)) {
38073
+ throw new AllocationError("invalid_amount", "Enter a positive USDC amount in whole cents.");
38074
+ }
38075
+ const [whole = "0", fraction = ""] = raw.split(".");
38076
+ const cents = BigInt(whole) * 100n + BigInt(fraction.padEnd(2, "0") || "0");
38077
+ if (cents <= 0n) {
38078
+ throw new AllocationError("invalid_amount", "Enter a positive USDC amount in whole cents.");
38079
+ }
38080
+ return cents;
38081
+ }
38082
+
38083
+ // src/movement-open.ts
38084
+ function isOpenMovementLeg(leg, journalEvidence) {
38085
+ if (leg.status === "sent" || leg.status === "cancelled") return false;
38086
+ if (leg.restored === true) return true;
38087
+ if (leg.status === "planned" || leg.status === "unknown") return true;
38088
+ if (leg.status !== "failed") return false;
38089
+ if (leg.retryable === true) return true;
38090
+ return journalEvidence === "signed";
38091
+ }
38092
+
38093
+ // src/spend-policy.ts
38094
+ import { randomBytes as randomBytes4 } from "node:crypto";
38095
+ import { mkdir as mkdir6, open as open5, readFile as readFile5, rename as rename3, stat as stat5, unlink as unlink4, writeFile as writeFile2 } from "node:fs/promises";
38096
+ import { dirname as dirname4 } from "node:path";
37435
38097
 
37436
38098
  // src/wallet-name.ts
37437
38099
  var DEFAULT_WALLET_NAME = "main";
@@ -37441,11 +38103,363 @@ var walletNameSchema = external_exports.string().regex(
37441
38103
  "A wallet name is 1 to 32 characters of lowercase letters, digits and dashes, starting with a letter or digit."
37442
38104
  );
37443
38105
 
38106
+ // src/spend-policy.ts
38107
+ var ledgerSchema = external_exports.object({
38108
+ date: external_exports.string().regex(/^\d{4}-\d{2}-\d{2}$/),
38109
+ spentAtomic: external_exports.string().regex(/^\d+$/)
38110
+ });
38111
+ var spendReservationSchema = external_exports.object({
38112
+ id: external_exports.string().min(1),
38113
+ amountAtomic: external_exports.string().regex(/^\d+$/),
38114
+ kind: external_exports.enum(["payment", "escrow-funding", "dispute-fee"]).optional(),
38115
+ taskKey: external_exports.string().optional()
38116
+ });
38117
+ var ledgerRowSchema = ledgerSchema.extend({
38118
+ wallet: walletNameSchema.optional(),
38119
+ reservations: external_exports.array(spendReservationSchema).optional()
38120
+ });
38121
+ var ledgerFileSchema = external_exports.union([
38122
+ external_exports.object({ version: external_exports.literal(1), rows: external_exports.array(ledgerRowSchema) }),
38123
+ ledgerRowSchema
38124
+ ]);
38125
+ var SpendCapError = class extends Error {
38126
+ constructor(code, message) {
38127
+ super(message);
38128
+ this.code = code;
38129
+ this.name = "SpendCapError";
38130
+ }
38131
+ code;
38132
+ };
38133
+ function utcDateKey(now = /* @__PURE__ */ new Date()) {
38134
+ return now.toISOString().slice(0, 10);
38135
+ }
38136
+ function sumTaskReservations(rows, wallet, taskKey, excludeReservationId) {
38137
+ let total = 0n;
38138
+ for (const row of rows) {
38139
+ if (row.wallet !== wallet) continue;
38140
+ for (const reservation of row.reservations ?? []) {
38141
+ if (reservation.taskKey === taskKey && reservation.id !== excludeReservationId)
38142
+ total += BigInt(reservation.amountAtomic);
38143
+ }
38144
+ }
38145
+ return total;
38146
+ }
38147
+ async function readStoredSpendLedgerRows(path, now, allDays = false) {
38148
+ let raw;
38149
+ try {
38150
+ raw = await readFile5(path, "utf8");
38151
+ } catch (error61) {
38152
+ if (isMissingFile(error61)) return [];
38153
+ throw error61;
38154
+ }
38155
+ const parsed = ledgerFileSchema.parse(JSON.parse(raw));
38156
+ const rows = "rows" in parsed ? parsed.rows : [parsed];
38157
+ const today = utcDateKey(now);
38158
+ return rows.filter((row) => allDays || row.date === today).map((row) => ({
38159
+ date: row.date,
38160
+ spentAtomic: row.spentAtomic,
38161
+ wallet: row.wallet ?? DEFAULT_WALLET_NAME,
38162
+ ...row.reservations === void 0 ? {} : { reservations: row.reservations }
38163
+ }));
38164
+ }
38165
+ async function reserveSpend(amountAtomic, caps, options) {
38166
+ if (amountAtomic < 0n) {
38167
+ throw new Error("Spend amount cannot be negative.");
38168
+ }
38169
+ const kind = options?.kind ?? "payment";
38170
+ let perDayAtomic;
38171
+ if (kind === "escrow-funding" || kind === "dispute-fee") {
38172
+ const maxPerTaskAtomic = options?.maxPerTaskAtomic;
38173
+ if (maxPerTaskAtomic === void 0 || maxPerTaskAtomic < 0n) {
38174
+ throw new Error("Escrow funding requires a non-negative per-task cap.");
38175
+ }
38176
+ if (amountAtomic > maxPerTaskAtomic) {
38177
+ throw new SpendCapError(
38178
+ "per_task_cap_exceeded",
38179
+ `Escrow funding ${amountAtomic} atomic USDC exceeds the per-task cap ${maxPerTaskAtomic}. Refusing to sign.`
38180
+ );
38181
+ }
38182
+ perDayAtomic = parseAtomicCap(caps.perDayAtomic, "per-day");
38183
+ } else {
38184
+ const perCallAtomic = parseAtomicCap(caps.perCallAtomic, "per-call");
38185
+ perDayAtomic = parseAtomicCap(caps.perDayAtomic, "per-day");
38186
+ if (amountAtomic > perCallAtomic) {
38187
+ throw new SpendCapError(
38188
+ "per_call_cap_exceeded",
38189
+ `Payment quote ${amountAtomic} atomic USDC exceeds the per-call cap ${perCallAtomic}. Refusing to sign.`
38190
+ );
38191
+ }
38192
+ }
38193
+ const ledgerPath = options?.ledgerPath ?? getAgentCashPaths().ledger;
38194
+ const now = options?.now ?? /* @__PURE__ */ new Date();
38195
+ const wallet = options?.wallet ?? DEFAULT_WALLET_NAME;
38196
+ return await withLedgerLock(ledgerPath, async () => {
38197
+ const rows = await readStoredSpendLedgerRows(ledgerPath, now, true);
38198
+ const today = utcDateKey(now);
38199
+ const current = rows.find((row) => row.wallet === wallet && row.date === today);
38200
+ if ((kind === "escrow-funding" || kind === "dispute-fee") && options?.taskKey !== void 0) {
38201
+ const taskTotal = sumTaskReservations(
38202
+ rows,
38203
+ wallet,
38204
+ options.taskKey,
38205
+ options.reuseExistingEscrowReservation || options.resumeEscrowReservation ? options.reservationId : void 0
38206
+ ) + amountAtomic;
38207
+ if (taskTotal > options.maxPerTaskAtomic)
38208
+ throw new SpendCapError(
38209
+ "per_task_cap_exceeded",
38210
+ `New spend ${amountAtomic} atomic USDC would raise the running total for task ${options.taskKey} and wallet ${wallet} to ${taskTotal}, above the per-task cap ${options.maxPerTaskAtomic}. Refusing to sign.`
38211
+ );
38212
+ }
38213
+ if (options?.reservationId !== void 0) {
38214
+ const existing = current?.reservations?.find(
38215
+ (reservation) => reservation.id === options.reservationId
38216
+ );
38217
+ if (existing && options.reuseExistingEscrowReservation) {
38218
+ if (kind !== "escrow-funding" && kind !== "dispute-fee" || existing.kind !== kind || existing.amountAtomic !== amountAtomic.toString() || existing.taskKey !== void 0 && existing.taskKey !== options.taskKey)
38219
+ throw new Error(
38220
+ `Spend reservation ${options.reservationId} does not match this funding.`
38221
+ );
38222
+ if (existing.taskKey === void 0 && options.taskKey !== void 0) {
38223
+ existing.taskKey = options.taskKey;
38224
+ await writeLedgerAtomically(ledgerPath, rows);
38225
+ }
38226
+ return {
38227
+ date: current.date,
38228
+ spentAtomic: current.spentAtomic,
38229
+ reservationReused: true
38230
+ };
38231
+ }
38232
+ if (existing) {
38233
+ throw new Error(`Spend reservation ${options.reservationId} already exists.`);
38234
+ }
38235
+ if (options.resumeEscrowReservation) {
38236
+ const resumed = options.resumeEscrowReservation;
38237
+ const historical = rows.find((row) => row.wallet === wallet && row.date === resumed.date);
38238
+ const stored = historical?.reservations?.find(
38239
+ (reservation) => reservation.id === resumed.id
38240
+ );
38241
+ if (kind !== "escrow-funding" && kind !== "dispute-fee" || resumed.id !== options.reservationId || resumed.wallet !== wallet || resumed.amountAtomic !== amountAtomic.toString() || resumed.date >= utcDateKey(now) || resumed.exposed !== true || resumed.invalidated === true || stored !== void 0 && (stored.kind !== kind || stored.amountAtomic !== amountAtomic.toString() || stored.taskKey !== void 0 && stored.taskKey !== options.taskKey))
38242
+ throw new Error(
38243
+ `Spend reservation ${options.reservationId} does not match this funding.`
38244
+ );
38245
+ if (stored?.taskKey === void 0 && options.taskKey !== void 0) {
38246
+ if (stored) stored.taskKey = options.taskKey;
38247
+ else {
38248
+ const reservation = {
38249
+ id: resumed.id,
38250
+ amountAtomic: resumed.amountAtomic,
38251
+ kind,
38252
+ taskKey: options.taskKey
38253
+ };
38254
+ if (historical)
38255
+ historical.reservations = [...historical.reservations ?? [], reservation];
38256
+ else
38257
+ rows.push({
38258
+ date: resumed.date,
38259
+ spentAtomic: resumed.amountAtomic,
38260
+ wallet,
38261
+ reservations: [reservation]
38262
+ });
38263
+ }
38264
+ await writeLedgerAtomically(ledgerPath, rows);
38265
+ }
38266
+ return {
38267
+ date: resumed.date,
38268
+ spentAtomic: current?.spentAtomic ?? "0",
38269
+ reservationReused: true
38270
+ };
38271
+ }
38272
+ }
38273
+ const spentAtomic = current?.spentAtomic ?? "0";
38274
+ const nextSpent = BigInt(spentAtomic) + amountAtomic;
38275
+ if (nextSpent > perDayAtomic) {
38276
+ throw new SpendCapError(
38277
+ "per_day_cap_exceeded",
38278
+ `${kind === "escrow-funding" ? "Escrow funding" : "Payment quote"} ${amountAtomic} atomic USDC would raise today's spend to ${nextSpent} for wallet ${wallet}, above the per-day cap ${perDayAtomic}. Refusing to sign.`
38279
+ );
38280
+ }
38281
+ const reservationId = options?.reservationId ?? (options?.taskKey === void 0 ? void 0 : randomBytes4(16).toString("hex"));
38282
+ const nextRow = {
38283
+ date: utcDateKey(now),
38284
+ spentAtomic: nextSpent.toString(),
38285
+ wallet,
38286
+ ...(current?.reservations?.length ?? 0) === 0 && reservationId === void 0 ? {} : {
38287
+ reservations: [
38288
+ ...current?.reservations ?? [],
38289
+ ...reservationId === void 0 ? [] : [
38290
+ {
38291
+ id: reservationId,
38292
+ amountAtomic: amountAtomic.toString(),
38293
+ ...kind !== "payment" ? { kind } : {},
38294
+ ...options?.taskKey === void 0 ? {} : { taskKey: options.taskKey }
38295
+ }
38296
+ ]
38297
+ ]
38298
+ }
38299
+ };
38300
+ const otherRows = rows.filter((row) => row.wallet !== wallet || row.date !== today).map((row) => {
38301
+ if (row.wallet !== wallet || !options?.reuseExistingEscrowReservation || options.reservationId === void 0)
38302
+ return row;
38303
+ const previous = row.reservations?.find(
38304
+ (reservation) => reservation.id === options.reservationId
38305
+ );
38306
+ if (!previous) return row;
38307
+ if (kind !== "escrow-funding" && kind !== "dispute-fee" || previous.kind !== kind || previous.amountAtomic !== amountAtomic.toString() || previous.taskKey !== void 0 && previous.taskKey !== options.taskKey)
38308
+ throw new Error(
38309
+ `Spend reservation ${options.reservationId} does not match this funding.`
38310
+ );
38311
+ return {
38312
+ ...row,
38313
+ reservations: row.reservations.filter(
38314
+ (reservation) => reservation.id !== options.reservationId
38315
+ )
38316
+ };
38317
+ });
38318
+ const nextRows = [...otherRows, nextRow].sort(
38319
+ (a, b) => a.wallet.localeCompare(b.wallet) || a.date.localeCompare(b.date)
38320
+ );
38321
+ await writeLedgerAtomically(ledgerPath, nextRows);
38322
+ return {
38323
+ date: nextRow.date,
38324
+ spentAtomic: nextRow.spentAtomic,
38325
+ ...options?.reservationId === void 0 && reservationId !== void 0 ? { reservationId } : {}
38326
+ };
38327
+ });
38328
+ }
38329
+ async function releaseSpend(amountAtomic, options) {
38330
+ if (amountAtomic < 0n) {
38331
+ throw new Error("Spend amount cannot be negative.");
38332
+ }
38333
+ const ledgerPath = options.ledgerPath ?? getAgentCashPaths().ledger;
38334
+ const now = options.now ?? /* @__PURE__ */ new Date();
38335
+ const wallet = options.wallet ?? DEFAULT_WALLET_NAME;
38336
+ return await withLedgerLock(ledgerPath, async () => {
38337
+ const today = utcDateKey(now);
38338
+ const rows = await readStoredSpendLedgerRows(ledgerPath, now, true);
38339
+ const current = rows.find((row) => row.wallet === wallet && row.date === today);
38340
+ if (options.reservedOn !== today && options.reservationId === void 0) {
38341
+ return { date: today, spentAtomic: current?.spentAtomic ?? "0" };
38342
+ }
38343
+ const reserved = rows.find((row) => row.wallet === wallet && row.date === options.reservedOn);
38344
+ const reservation = options.reservationId === void 0 ? void 0 : reserved?.reservations?.find((candidate) => candidate.id === options.reservationId);
38345
+ if (options.reservationId !== void 0 && reservation === void 0) {
38346
+ return { date: today, spentAtomic: current?.spentAtomic ?? "0" };
38347
+ }
38348
+ if (reservation !== void 0 && reservation.amountAtomic !== amountAtomic.toString()) {
38349
+ throw new Error(`Spend reservation ${options.reservationId} has a different amount.`);
38350
+ }
38351
+ const spentAtomic = BigInt(reserved?.spentAtomic ?? "0");
38352
+ const releasedAmount = reservation === void 0 ? amountAtomic : BigInt(reservation.amountAtomic);
38353
+ const nextSpent = spentAtomic > releasedAmount ? spentAtomic - releasedAmount : 0n;
38354
+ const remainingReservations = reserved?.reservations?.filter(
38355
+ (candidate) => candidate.id !== options.reservationId
38356
+ );
38357
+ const nextRow = {
38358
+ date: options.reservedOn,
38359
+ spentAtomic: nextSpent.toString(),
38360
+ wallet,
38361
+ ...remainingReservations === void 0 || remainingReservations.length === 0 ? {} : { reservations: remainingReservations }
38362
+ };
38363
+ const nextRows = [
38364
+ ...rows.filter((row) => row.wallet !== wallet || row.date !== options.reservedOn),
38365
+ nextRow
38366
+ ].sort((a, b) => a.wallet.localeCompare(b.wallet) || a.date.localeCompare(b.date));
38367
+ await writeLedgerAtomically(ledgerPath, nextRows);
38368
+ return {
38369
+ date: today,
38370
+ spentAtomic: options.reservedOn === today ? nextRow.spentAtomic : current?.spentAtomic ?? "0"
38371
+ };
38372
+ });
38373
+ }
38374
+ function parseAtomicCap(value, label) {
38375
+ if (!/^\d+$/.test(value)) {
38376
+ throw new Error(`Invalid ${label} spend cap ${JSON.stringify(value)}.`);
38377
+ }
38378
+ return BigInt(value);
38379
+ }
38380
+ async function writeLedgerAtomically(path, rows) {
38381
+ const ledger = rows.length === 1 && rows[0].wallet === DEFAULT_WALLET_NAME ? {
38382
+ date: rows[0].date,
38383
+ spentAtomic: rows[0].spentAtomic,
38384
+ ...rows[0].reservations === void 0 ? {} : { reservations: rows[0].reservations }
38385
+ } : { version: 1, rows };
38386
+ await mkdir6(dirname4(path), { recursive: true, mode: 448 });
38387
+ const temporaryPath = `${path}.${process.pid}.${randomBytes4(6).toString("hex")}.tmp`;
38388
+ await writeFile2(temporaryPath, `${JSON.stringify(ledger, null, 2)}
38389
+ `, {
38390
+ encoding: "utf8",
38391
+ mode: 384,
38392
+ flag: "wx"
38393
+ });
38394
+ await rename3(temporaryPath, path);
38395
+ }
38396
+ async function withLedgerLock(path, operation) {
38397
+ await mkdir6(dirname4(path), { recursive: true, mode: 448 });
38398
+ const lockPath = `${path}.lock`;
38399
+ const deadline = Date.now() + 2e3;
38400
+ let handle;
38401
+ while (!handle) {
38402
+ try {
38403
+ handle = await open5(lockPath, "wx", 384);
38404
+ } catch (error61) {
38405
+ if (!(error61 instanceof Error && "code" in error61 && error61.code === "EEXIST")) {
38406
+ throw error61;
38407
+ }
38408
+ await removeStaleLock3(lockPath);
38409
+ if (Date.now() >= deadline) {
38410
+ throw new Error("Timed out waiting for the spend ledger lock.");
38411
+ }
38412
+ await new Promise((resolve) => setTimeout(resolve, 20));
38413
+ }
38414
+ }
38415
+ try {
38416
+ return await operation();
38417
+ } finally {
38418
+ await handle.close();
38419
+ await unlink4(lockPath).catch(() => void 0);
38420
+ }
38421
+ }
38422
+ async function removeStaleLock3(path) {
38423
+ try {
38424
+ const metadata = await stat5(path);
38425
+ if (Date.now() - metadata.mtimeMs > 3e4) {
38426
+ await unlink4(path);
38427
+ }
38428
+ } catch (error61) {
38429
+ if (!isMissingFile(error61)) {
38430
+ throw error61;
38431
+ }
38432
+ }
38433
+ }
38434
+
38435
+ // src/transfer-journal.ts
38436
+ import { readFile as readFile7 } from "node:fs/promises";
38437
+
38438
+ // src/receipts.ts
38439
+ import { mkdir as mkdir7, open as open6, readFile as readFile6, rename as rename4, writeFile as writeFile3 } from "node:fs/promises";
38440
+ import { dirname as dirname5 } from "node:path";
38441
+
38442
+ // src/agent-run.ts
38443
+ var RUN_ID_PATTERN = /^run_[0-9a-f]{24}$/;
38444
+ var runRefSchema = external_exports.strictObject({
38445
+ id: external_exports.string().regex(RUN_ID_PATTERN),
38446
+ swarm: external_exports.string().optional(),
38447
+ member: external_exports.string().optional(),
38448
+ parentRunId: external_exports.string().optional()
38449
+ });
38450
+
37444
38451
  // src/receipts.ts
38452
+ var RECEIPT_JOURNAL_BUSY_MESSAGE = "The receipt journal is busy. Wait for the other vapi command to finish, then retry.";
38453
+ async function withReceiptJournalLock(path, operation) {
38454
+ return await withFileLock(`${path}.lock`, operation, {
38455
+ lockedMessage: RECEIPT_JOURNAL_BUSY_MESSAGE
38456
+ });
38457
+ }
37445
38458
  var durationSchema = external_exports.number().nonnegative().finite();
37446
38459
  var receiptSchema = external_exports.strictObject({
37447
38460
  id: external_exports.string().min(1),
37448
38461
  timestamp: external_exports.iso.datetime(),
38462
+ run: runRefSchema.optional(),
37449
38463
  kind: external_exports.literal("transfer").optional(),
37450
38464
  wallet: walletNameSchema.optional(),
37451
38465
  resourceUrl: external_exports.url(),
@@ -37552,20 +38566,22 @@ async function appendReceipt(receipt, path = getVapiPaths().receipts, options =
37552
38566
  const parsed = parseReceipt(
37553
38567
  receipt.wallet === void 0 && options.wallet !== void 0 ? { ...receipt, wallet: options.wallet } : receipt
37554
38568
  );
37555
- await mkdir3(dirname3(path), { recursive: true, mode: 448 });
37556
- const handle = await open3(path, "a", 384);
37557
- try {
37558
- await handle.writeFile(`${JSON.stringify(parsed)}
38569
+ await withReceiptJournalLock(path, async () => {
38570
+ await mkdir7(dirname5(path), { recursive: true, mode: 448 });
38571
+ const handle = await open6(path, "a", 384);
38572
+ try {
38573
+ await handle.writeFile(`${JSON.stringify(parsed)}
37559
38574
  `, "utf8");
37560
- } finally {
37561
- await handle.close();
37562
- }
38575
+ } finally {
38576
+ await handle.close();
38577
+ }
38578
+ });
37563
38579
  return parsed;
37564
38580
  }
37565
38581
  async function readReceipts(path = getVapiPaths().receipts, options = {}) {
37566
38582
  let raw;
37567
38583
  try {
37568
- raw = await readFile2(path, "utf8");
38584
+ raw = await readFile6(path, "utf8");
37569
38585
  } catch (error61) {
37570
38586
  if (isMissingFile(error61)) return [];
37571
38587
  throw error61;
@@ -37607,6 +38623,45 @@ function filterReceiptsByWallet(receipts, name) {
37607
38623
  return receipts.filter((receipt) => receiptWallet(receipt) === name);
37608
38624
  }
37609
38625
 
38626
+ // src/transfer-journal.ts
38627
+ async function readTransferJournal(home, from15, nonce) {
38628
+ let raw;
38629
+ try {
38630
+ raw = await readFile7(getVapiPaths(home).receipts, "utf8");
38631
+ } catch (error61) {
38632
+ if (isMissingFile(error61)) return { signed: false };
38633
+ throw error61;
38634
+ }
38635
+ let latest;
38636
+ let request;
38637
+ for (const [index2, line] of raw.split("\n").entries()) {
38638
+ if (!line.trim()) continue;
38639
+ let receipt;
38640
+ try {
38641
+ receipt = parseReceipt(JSON.parse(line));
38642
+ } catch (error61) {
38643
+ throw new Error(`Invalid receipt on JSONL line ${index2 + 1}.`, { cause: error61 });
38644
+ }
38645
+ if (receiptWallet(receipt) !== from15 || receipt.transfer?.nonce.toLowerCase() !== nonce.toLowerCase()) {
38646
+ continue;
38647
+ }
38648
+ latest = receipt;
38649
+ if (receipt.transfer.request !== void 0) request = receipt;
38650
+ }
38651
+ return {
38652
+ signed: latest !== void 0,
38653
+ ...latest === void 0 ? {} : { latest },
38654
+ ...request === void 0 ? {} : { request },
38655
+ ...request?.transfer?.request?.authorization.validBefore === void 0 ? {} : { validBefore: request.transfer.request.authorization.validBefore },
38656
+ ...(latest?.transfer?.reservedOn ?? request?.transfer?.reservedOn) === void 0 ? {} : { reservedOn: latest?.transfer?.reservedOn ?? request?.transfer?.reservedOn }
38657
+ };
38658
+ }
38659
+
38660
+ // src/transfer.ts
38661
+ import { randomBytes as randomBytes5, randomUUID } from "node:crypto";
38662
+ import { mkdir as mkdir9, open as open8, stat as stat7, unlink as unlink6 } from "node:fs/promises";
38663
+ import { dirname as dirname6 } from "node:path";
38664
+
37610
38665
  // src/siblings.ts
37611
38666
  var SiblingsError = class extends Error {
37612
38667
  constructor(code, message, status) {
@@ -37645,8 +38700,8 @@ async function fetchSiblings(args) {
37645
38700
  try {
37646
38701
  response = await withAgentCredentialLock(args.wallets, `wallet:${args.account}`, async () => {
37647
38702
  await args.wallets.reload();
37648
- const link = args.wallets.entry(args.account)?.link;
37649
- if (link === void 0 || new URL(link.apiBase).origin !== new URL(args.apiBase).origin) {
38703
+ const link2 = args.wallets.entry(args.account)?.link;
38704
+ if (link2 === void 0 || new URL(link2.apiBase).origin !== new URL(args.apiBase).origin) {
37650
38705
  throw new SiblingsError(
37651
38706
  "not_linked",
37652
38707
  "The selected account is not linked to this vAPI server."
@@ -37660,7 +38715,7 @@ async function fetchSiblings(args) {
37660
38715
  ...args.fetchImpl === void 0 ? {} : { fetchImpl: args.fetchImpl },
37661
38716
  ...args.now === void 0 ? {} : { now: args.now }
37662
38717
  },
37663
- new URL("/api/agents/self/siblings", new URL(link.apiBase).origin).toString(),
38718
+ new URL("/api/agents/self/siblings", new URL(link2.apiBase).origin).toString(),
37664
38719
  { method: "GET", signal }
37665
38720
  );
37666
38721
  });
@@ -37699,6 +38754,20 @@ function invalidResponseError() {
37699
38754
  return new SiblingsError("invalid_response", "The vAPI siblings response was invalid.");
37700
38755
  }
37701
38756
 
38757
+ // src/wallet-store.ts
38758
+ import {
38759
+ chmod as chmod2,
38760
+ lstat,
38761
+ mkdir as mkdir8,
38762
+ open as open7,
38763
+ readdir as readdir2,
38764
+ readFile as readFile8,
38765
+ rename as rename5,
38766
+ stat as stat6,
38767
+ unlink as unlink5,
38768
+ writeFile as writeFile4
38769
+ } from "node:fs/promises";
38770
+
37702
38771
  // ../../node_modules/.pnpm/@solana+errors@5.5.1_typescript@6.0.3/node_modules/@solana/errors/dist/index.node.mjs
37703
38772
  var SOLANA_ERROR__BLOCK_HEIGHT_EXCEEDED = 1;
37704
38773
  var SOLANA_ERROR__INVALID_NONCE = 2;
@@ -41618,6 +42687,62 @@ async function signModifyingAndPartialTransactionSigners(transactionMessage, mod
41618
42687
  }
41619
42688
  var o2 = globalThis.TextEncoder;
41620
42689
 
42690
+ // src/wallet-store.ts
42691
+ var agentLinkSchema = external_exports.object({
42692
+ apiBase: external_exports.string(),
42693
+ clientId: external_exports.string(),
42694
+ owner: external_exports.custom((value) => typeof value === "string" && value.startsWith("0x")),
42695
+ label: external_exports.string(),
42696
+ scopes: external_exports.array(external_exports.string()),
42697
+ linkedAt: external_exports.iso.datetime(),
42698
+ routerBaseUrl: external_exports.string().optional()
42699
+ });
42700
+ var ROUTER_TOPUP_TIERS = [1, 5, 20, 50];
42701
+ var routerRefillSchema = external_exports.object({
42702
+ belowUsd: external_exports.number().finite().min(0),
42703
+ tierUsd: external_exports.union([external_exports.literal(1), external_exports.literal(5), external_exports.literal(20), external_exports.literal(50)])
42704
+ });
42705
+ var ceilingSweepPendingSchema = external_exports.strictObject({
42706
+ owner: external_exports.string().regex(/^0x[0-9a-fA-F]{40}$/),
42707
+ target: external_exports.string().regex(/^0x[0-9a-fA-F]{40}$/).optional(),
42708
+ amountAtomic: external_exports.string().regex(/^\d+$/),
42709
+ nonce: external_exports.string().regex(/^0x[0-9a-fA-F]{64}$/),
42710
+ createdAt: external_exports.iso.datetime(),
42711
+ status: external_exports.enum(["planned", "unknown"])
42712
+ });
42713
+ var walletEntrySchema = external_exports.object({
42714
+ createdAt: external_exports.iso.datetime(),
42715
+ label: external_exports.string().trim().min(1).max(80).optional(),
42716
+ spendCaps: spendCapsSchema,
42717
+ // On disk, null explicitly disables ceiling sweeps; an absent field is the
42718
+ // backwards-compatible 5 USDC default and is not written during reads.
42719
+ ceilingAtomic: external_exports.string().regex(/^\d+$/).nullable().optional(),
42720
+ ceilingSweepAttemptedAt: external_exports.iso.datetime().optional(),
42721
+ ceilingSweepPending: ceilingSweepPendingSchema.optional(),
42722
+ routerRefill: routerRefillSchema.optional(),
42723
+ link: agentLinkSchema.optional()
42724
+ });
42725
+ var walletRegistrySchema = external_exports.object({
42726
+ version: external_exports.literal(1),
42727
+ default: walletNameSchema.optional(),
42728
+ wallets: external_exports.record(walletNameSchema, walletEntrySchema)
42729
+ });
42730
+ async function spendCapsForWallet(store, name = DEFAULT_WALLET_NAME) {
42731
+ const entry = store.entry(name);
42732
+ if (entry) return { ...entry.spendCaps };
42733
+ return await readConfiguredSpendCaps(getVapiPaths(store.home).config);
42734
+ }
42735
+ async function readConfiguredSpendCaps(configPath) {
42736
+ try {
42737
+ const parsed = JSON.parse(await readFile8(configPath, "utf8"));
42738
+ const caps = typeof parsed === "object" && parsed !== null ? Reflect.get(parsed, "spendCaps") : void 0;
42739
+ const result = spendCapsSchema.safeParse(caps);
42740
+ if (result.success) return result.data;
42741
+ } catch {
42742
+ }
42743
+ return { ...DEFAULT_SPEND_CAPS };
42744
+ }
42745
+
41621
42746
  // ../../node_modules/.pnpm/@solana-program+token-2022@0.6.1_@solana+kit@5.5.1_typescript@6.0.3__@solana+sysvars@5.5.1_typescript@6.0.3_/node_modules/@solana-program/token-2022/dist/src/index.mjs
41622
42747
  var AccountState = /* @__PURE__ */ ((AccountState2) => {
41623
42748
  AccountState2[AccountState2["Uninitialized"] = 0] = "Uninitialized";
@@ -42627,6 +43752,104 @@ var ExactSvmScheme = class {
42627
43752
  var addressEncoder = getAddressEncoder();
42628
43753
  var compiledMessageDecoder = getCompiledTransactionMessageDecoder();
42629
43754
 
43755
+ // src/svm.ts
43756
+ var MAX_RPC_RESPONSE_BYTES = 4 * 1024 * 1024;
43757
+ function isSolanaAddress(value) {
43758
+ try {
43759
+ address(value);
43760
+ return true;
43761
+ } catch {
43762
+ return false;
43763
+ }
43764
+ }
43765
+ async function readSolanaUsdcBalance(args) {
43766
+ const accounts = await readSolanaUsdcAccounts(args);
43767
+ return accounts.reduce((total, account) => total + account.amountAtomic, 0n);
43768
+ }
43769
+ async function readSolanaUsdcAccounts(args) {
43770
+ if (!isSolanaAddress(args.address)) {
43771
+ throw new Error(`Invalid Solana address: ${args.address}.`);
43772
+ }
43773
+ const rpcUrl = requireRpcUrl(args.network, args.configured);
43774
+ const value = await solanaRpcCall(
43775
+ rpcUrl,
43776
+ "getTokenAccountsByOwner",
43777
+ [
43778
+ args.address,
43779
+ { mint: args.configured.usdc },
43780
+ { encoding: "jsonParsed", commitment: "confirmed" }
43781
+ ],
43782
+ rpcOptionsFrom(args)
43783
+ );
43784
+ return value.map((entry) => {
43785
+ const info = entry.account.data.parsed.info;
43786
+ if (!isSolanaAddress(entry.pubkey) || info.mint !== args.configured.usdc || info.owner !== args.address || info.tokenAmount.decimals !== USDC_DECIMALS || !/^\d+$/.test(info.tokenAmount.amount)) {
43787
+ throw new Error("Solana RPC returned a malformed USDC token account.");
43788
+ }
43789
+ return { address: entry.pubkey, amountAtomic: BigInt(info.tokenAmount.amount) };
43790
+ });
43791
+ }
43792
+ async function solanaRpcCall(rpcUrl, method, params, options) {
43793
+ const fetchImpl = options.fetchImpl ?? createPublicFetch({
43794
+ allowPrivateNetwork: options.allowPrivateNetwork ?? false,
43795
+ ...options.lookup ? { lookup: options.lookup } : {}
43796
+ });
43797
+ const response = await fetchImpl(rpcUrl, {
43798
+ method: "POST",
43799
+ headers: { "content-type": "application/json" },
43800
+ body: JSON.stringify({ jsonrpc: "2.0", id: 1, method, params }),
43801
+ redirect: "manual"
43802
+ });
43803
+ if (!response.ok) {
43804
+ discardBody(response);
43805
+ const rateLimit = response.status === 429 ? " (public RPC rate limit)" : "";
43806
+ throw new Error(`Solana RPC ${method} failed with HTTP ${response.status}${rateLimit}.`);
43807
+ }
43808
+ const envelope = await readBoundedRpcJson(response);
43809
+ if (!isRecord2(envelope) || envelope.jsonrpc !== "2.0") {
43810
+ throw new Error(`Solana RPC ${method} returned an invalid JSON-RPC envelope.`);
43811
+ }
43812
+ if (envelope.error !== void 0) {
43813
+ throw new Error(`Solana RPC ${method} failed: ${JSON.stringify(envelope.error)}.`);
43814
+ }
43815
+ const result = envelope.result;
43816
+ if (isRecord2(result) && "value" in result) return result.value;
43817
+ return result;
43818
+ }
43819
+ async function readBoundedRpcJson(response) {
43820
+ if (!response.body) throw new Error("Solana RPC response body is empty.");
43821
+ const reader = response.body.getReader();
43822
+ const chunks = [];
43823
+ let length = 0;
43824
+ while (true) {
43825
+ const next = await reader.read();
43826
+ if (next.done) break;
43827
+ length += next.value.byteLength;
43828
+ if (length > MAX_RPC_RESPONSE_BYTES) {
43829
+ await reader.cancel();
43830
+ throw new Error(`Solana RPC response exceeds ${MAX_RPC_RESPONSE_BYTES} bytes.`);
43831
+ }
43832
+ chunks.push(next.value);
43833
+ }
43834
+ const bytes = new Uint8Array(length);
43835
+ let offset = 0;
43836
+ for (const chunk of chunks) {
43837
+ bytes.set(chunk, offset);
43838
+ offset += chunk.byteLength;
43839
+ }
43840
+ return JSON.parse(new TextDecoder().decode(bytes));
43841
+ }
43842
+ function rpcOptionsFrom(options) {
43843
+ return {
43844
+ allowPrivateNetwork: options.allowPrivateNetwork,
43845
+ ...options.fetchImpl ? { fetchImpl: options.fetchImpl } : {},
43846
+ ...options.lookup ? { lookup: options.lookup } : {}
43847
+ };
43848
+ }
43849
+ function isRecord2(value) {
43850
+ return typeof value === "object" && value !== null && !Array.isArray(value);
43851
+ }
43852
+
42630
43853
  // src/x402.ts
42631
43854
  var MAX_CHALLENGE_BYTES = 1048576;
42632
43855
  var MAX_UINT256 = (1n << 256n) - 1n;
@@ -42659,15 +43882,15 @@ var X402Error = class extends Error {
42659
43882
  function isValidPaymentId(value) {
42660
43883
  return typeof value === "string" && PAYMENT_ID_PATTERN.test(value);
42661
43884
  }
42662
- function createPaymentId(randomBytes5 = fillRandomBytes) {
43885
+ function createPaymentId(randomBytes9 = fillRandomBytes) {
42663
43886
  const bytes = new Uint8Array(16);
42664
- randomBytes5(bytes);
43887
+ randomBytes9(bytes);
42665
43888
  return `pay_${bytesToHex(bytes).slice(2)}`;
42666
43889
  }
42667
43890
  function buildPaymentExtensions(serverExtensions, options = {}) {
42668
43891
  const declaredBuilderCode = serverExtensions?.["builder-code"];
42669
- const builderInfo = isRecord2(declaredBuilderCode) ? declaredBuilderCode.info : void 0;
42670
- const serverBuilderInfo = isRecord2(builderInfo) ? builderInfo : void 0;
43892
+ const builderInfo = isRecord3(declaredBuilderCode) ? declaredBuilderCode.info : void 0;
43893
+ const serverBuilderInfo = isRecord3(builderInfo) ? builderInfo : void 0;
42671
43894
  const declaredServerCodes = serverBuilderInfo?.s;
42672
43895
  const serverCodes = typeof declaredServerCodes === "string" ? [declaredServerCodes] : Array.isArray(declaredServerCodes) ? declaredServerCodes : void 0;
42673
43896
  const validServerCodes = [];
@@ -42684,10 +43907,10 @@ function buildPaymentExtensions(serverExtensions, options = {}) {
42684
43907
  ...typeof appCode === "string" && BUILDER_CODE_PATTERN.test(appCode) ? { a: appCode } : {},
42685
43908
  s: [VAPI_BUILDER_CODE, ...validServerCodes]
42686
43909
  },
42687
- ...isRecord2(declaredBuilderCode) && declaredBuilderCode.schema !== void 0 ? { schema: declaredBuilderCode.schema } : {}
43910
+ ...isRecord3(declaredBuilderCode) && declaredBuilderCode.schema !== void 0 ? { schema: declaredBuilderCode.schema } : {}
42688
43911
  };
42689
43912
  const paymentIdentifier = serverExtensions?.["payment-identifier"];
42690
- if (!isRecord2(paymentIdentifier)) {
43913
+ if (!isRecord3(paymentIdentifier)) {
42691
43914
  return { ...serverExtensions, "builder-code": builderCode };
42692
43915
  }
42693
43916
  if (!isValidPaymentId(options.paymentId)) {
@@ -42696,7 +43919,7 @@ function buildPaymentExtensions(serverExtensions, options = {}) {
42696
43919
  "paymentId must be 16-128 characters containing only letters, digits, underscores, or hyphens."
42697
43920
  );
42698
43921
  }
42699
- const paymentInfo = isRecord2(paymentIdentifier.info) ? paymentIdentifier.info : {};
43922
+ const paymentInfo = isRecord3(paymentIdentifier.info) ? paymentIdentifier.info : {};
42700
43923
  return {
42701
43924
  ...serverExtensions,
42702
43925
  "builder-code": builderCode,
@@ -42707,7 +43930,7 @@ function buildPaymentExtensions(serverExtensions, options = {}) {
42707
43930
  };
42708
43931
  }
42709
43932
  function parse402Challenge(challenge2, configuredNetworks, fallbackResourceUrl = "", requiredNetwork, expectedPayTo) {
42710
- if (!isRecord2(challenge2) || !Array.isArray(challenge2.accepts)) {
43933
+ if (!isRecord3(challenge2) || !Array.isArray(challenge2.accepts)) {
42711
43934
  throw new X402Error("invalid_challenge", "x402 challenge has no accepts array.");
42712
43935
  }
42713
43936
  if (challenge2.x402Version !== void 0 && challenge2.x402Version !== 2) {
@@ -42716,7 +43939,7 @@ function parse402Challenge(challenge2, configuredNetworks, fallbackResourceUrl =
42716
43939
  `Unsupported x402 version: ${String(challenge2.x402Version)}.`
42717
43940
  );
42718
43941
  }
42719
- const candidates = challenge2.accepts.filter(isRecord2);
43942
+ const candidates = challenge2.accepts.filter(isRecord3);
42720
43943
  const accepted = candidates.find((candidate) => {
42721
43944
  const network2 = readString(candidate, "network");
42722
43945
  const scheme = readString(candidate, "scheme");
@@ -42905,17 +44128,24 @@ function buildEip3009TypedData(args) {
42905
44128
  if (!Number.isSafeInteger(args.nowSeconds) || args.nowSeconds < 0) {
42906
44129
  throw new Error("nowSeconds must be a non-negative integer.");
42907
44130
  }
44131
+ if (args.maximumValidBeforeSeconds !== void 0 && (!Number.isSafeInteger(args.maximumValidBeforeSeconds) || args.maximumValidBeforeSeconds <= args.nowSeconds)) {
44132
+ throw new Error("The payment allowance has expired; no authorization was signed.");
44133
+ }
42908
44134
  if (!isEvmPaymentRequirements(args.quote.accepted)) {
42909
44135
  throw new Error("EIP-3009 typed data can only be built for an EVM x402 quote.");
42910
44136
  }
42911
44137
  const accepted = args.quote.accepted;
42912
44138
  const windowSeconds = Math.min(MAX_AUTHORIZATION_WINDOW_SECONDS, accepted.maxTimeoutSeconds);
44139
+ const validBefore = Math.min(
44140
+ args.nowSeconds + windowSeconds,
44141
+ args.maximumValidBeforeSeconds ?? Number.MAX_SAFE_INTEGER
44142
+ );
42913
44143
  const authorization = {
42914
44144
  from: getAddress(args.from),
42915
44145
  to: accepted.payTo,
42916
44146
  value: args.quote.amountAtomic.toString(),
42917
44147
  validAfter: "0",
42918
- validBefore: (args.nowSeconds + windowSeconds).toString(),
44148
+ validBefore: validBefore.toString(),
42919
44149
  nonce: args.nonce
42920
44150
  };
42921
44151
  return {
@@ -42941,7 +44171,7 @@ function buildEip3009TypedData(args) {
42941
44171
  };
42942
44172
  }
42943
44173
  async function buildX402Payment(args) {
42944
- const advertisesPaymentIdentifier = isRecord2(args.quote.extensions?.["payment-identifier"]);
44174
+ const advertisesPaymentIdentifier = isRecord3(args.quote.extensions?.["payment-identifier"]);
42945
44175
  const paymentId = advertisesPaymentIdentifier ? args.paymentId ?? createPaymentId() : void 0;
42946
44176
  const extensions = buildPaymentExtensions(args.quote.extensions, { paymentId });
42947
44177
  if (isSvmPaymentRequirements(args.quote.accepted)) {
@@ -42986,7 +44216,8 @@ async function buildX402Payment(args) {
42986
44216
  from: args.signer.address,
42987
44217
  quote: args.quote,
42988
44218
  nonce,
42989
- nowSeconds: args.nowSeconds ?? Math.floor(Date.now() / 1e3)
44219
+ nowSeconds: args.nowSeconds ?? Math.floor(Date.now() / 1e3),
44220
+ ...args.maximumValidBeforeSeconds === void 0 ? {} : { maximumValidBeforeSeconds: args.maximumValidBeforeSeconds }
42990
44221
  });
42991
44222
  const signature = await args.signer.signTypedData(typedData);
42992
44223
  const payload = {
@@ -43009,6 +44240,7 @@ async function buildCompatibleX402Payment(args) {
43009
44240
  signer: args.account,
43010
44241
  quote: args.quote,
43011
44242
  ...args.nowSeconds === void 0 ? {} : { nowSeconds: args.nowSeconds },
44243
+ ...args.maximumValidBeforeSeconds === void 0 ? {} : { maximumValidBeforeSeconds: args.maximumValidBeforeSeconds },
43012
44244
  ...args.nonce === void 0 ? {} : { nonce: args.nonce },
43013
44245
  ...args.fetchImpl === void 0 ? {} : { fetchImpl: args.fetchImpl },
43014
44246
  ...args.paymentId === void 0 ? {} : { paymentId: args.paymentId }
@@ -43068,7 +44300,7 @@ function parseSettlementResponse(headers) {
43068
44300
  }
43069
44301
  }
43070
44302
  function classifySettlement(value) {
43071
- if (!isRecord2(value) || typeof value.success !== "boolean") {
44303
+ if (!isRecord3(value) || typeof value.success !== "boolean") {
43072
44304
  return "unknown";
43073
44305
  }
43074
44306
  return value.success ? "succeeded" : "rejected";
@@ -43164,12 +44396,12 @@ function canonicalTokenDomain(network, configured) {
43164
44396
  return configuredDomain;
43165
44397
  }
43166
44398
  function readBoundedJsonRecord(value) {
43167
- if (!isRecord2(value)) return null;
44399
+ if (!isRecord3(value)) return null;
43168
44400
  const seen = /* @__PURE__ */ new WeakSet();
43169
44401
  const budget = { entries: 0 };
43170
44402
  try {
43171
44403
  const cloned = cloneBoundedJson(value, 0, seen, budget);
43172
- return isRecord2(cloned) ? cloned : null;
44404
+ return isRecord3(cloned) ? cloned : null;
43173
44405
  } catch {
43174
44406
  return null;
43175
44407
  }
@@ -43178,7 +44410,7 @@ function readChallengeExtensions(value) {
43178
44410
  if (value === void 0) return void 0;
43179
44411
  const extensions = readBoundedJsonRecord(value);
43180
44412
  if (extensions) return extensions;
43181
- if (!isRecord2(value)) return void 0;
44413
+ if (!isRecord3(value)) return void 0;
43182
44414
  const paymentExtensions = /* @__PURE__ */ Object.create(null);
43183
44415
  for (const name of ["builder-code", "payment-identifier"]) {
43184
44416
  const extension = readBoundedJsonRecord(value[name]);
@@ -43231,7 +44463,7 @@ function cloneBoundedJson(value, depth, seen, budget) {
43231
44463
  }
43232
44464
  function readResource(value, fallbackUrl) {
43233
44465
  if (typeof value === "string") return { url: value };
43234
- if (!isRecord2(value)) return { url: fallbackUrl };
44466
+ if (!isRecord3(value)) return { url: fallbackUrl };
43235
44467
  const url2 = readString(value, "url") ?? fallbackUrl;
43236
44468
  return {
43237
44469
  url: url2,
@@ -43246,7 +44478,7 @@ function readString(value, key) {
43246
44478
  const field = value[key];
43247
44479
  return typeof field === "string" && field.length > 0 ? field : null;
43248
44480
  }
43249
- function isRecord2(value) {
44481
+ function isRecord3(value) {
43250
44482
  return typeof value === "object" && value !== null && !Array.isArray(value);
43251
44483
  }
43252
44484
  function decodeBase64Json(value) {
@@ -43262,421 +44494,6 @@ function encodeBase64Json(value) {
43262
44494
  return globalThis.btoa(binary);
43263
44495
  }
43264
44496
 
43265
- // src/svm.ts
43266
- var MAX_RPC_RESPONSE_BYTES = 4 * 1024 * 1024;
43267
- function isSolanaAddress(value) {
43268
- try {
43269
- address(value);
43270
- return true;
43271
- } catch {
43272
- return false;
43273
- }
43274
- }
43275
- async function readSolanaUsdcBalance(args) {
43276
- const accounts = await readSolanaUsdcAccounts(args);
43277
- return accounts.reduce((total, account) => total + account.amountAtomic, 0n);
43278
- }
43279
- async function readSolanaUsdcAccounts(args) {
43280
- if (!isSolanaAddress(args.address)) {
43281
- throw new Error(`Invalid Solana address: ${args.address}.`);
43282
- }
43283
- const rpcUrl = requireRpcUrl(args.network, args.configured);
43284
- const value = await solanaRpcCall(
43285
- rpcUrl,
43286
- "getTokenAccountsByOwner",
43287
- [
43288
- args.address,
43289
- { mint: args.configured.usdc },
43290
- { encoding: "jsonParsed", commitment: "confirmed" }
43291
- ],
43292
- rpcOptionsFrom(args)
43293
- );
43294
- return value.map((entry) => {
43295
- const info = entry.account.data.parsed.info;
43296
- if (!isSolanaAddress(entry.pubkey) || info.mint !== args.configured.usdc || info.owner !== args.address || info.tokenAmount.decimals !== USDC_DECIMALS || !/^\d+$/.test(info.tokenAmount.amount)) {
43297
- throw new Error("Solana RPC returned a malformed USDC token account.");
43298
- }
43299
- return { address: entry.pubkey, amountAtomic: BigInt(info.tokenAmount.amount) };
43300
- });
43301
- }
43302
- async function solanaRpcCall(rpcUrl, method, params, options) {
43303
- const fetchImpl = options.fetchImpl ?? createPublicFetch({
43304
- allowPrivateNetwork: options.allowPrivateNetwork ?? false,
43305
- ...options.lookup ? { lookup: options.lookup } : {}
43306
- });
43307
- const response = await fetchImpl(rpcUrl, {
43308
- method: "POST",
43309
- headers: { "content-type": "application/json" },
43310
- body: JSON.stringify({ jsonrpc: "2.0", id: 1, method, params }),
43311
- redirect: "manual"
43312
- });
43313
- if (!response.ok) {
43314
- discardBody(response);
43315
- const rateLimit = response.status === 429 ? " (public RPC rate limit)" : "";
43316
- throw new Error(`Solana RPC ${method} failed with HTTP ${response.status}${rateLimit}.`);
43317
- }
43318
- const envelope = await readBoundedRpcJson(response);
43319
- if (!isRecord3(envelope) || envelope.jsonrpc !== "2.0") {
43320
- throw new Error(`Solana RPC ${method} returned an invalid JSON-RPC envelope.`);
43321
- }
43322
- if (envelope.error !== void 0) {
43323
- throw new Error(`Solana RPC ${method} failed: ${JSON.stringify(envelope.error)}.`);
43324
- }
43325
- const result = envelope.result;
43326
- if (isRecord3(result) && "value" in result) return result.value;
43327
- return result;
43328
- }
43329
- async function readBoundedRpcJson(response) {
43330
- if (!response.body) throw new Error("Solana RPC response body is empty.");
43331
- const reader = response.body.getReader();
43332
- const chunks = [];
43333
- let length = 0;
43334
- while (true) {
43335
- const next = await reader.read();
43336
- if (next.done) break;
43337
- length += next.value.byteLength;
43338
- if (length > MAX_RPC_RESPONSE_BYTES) {
43339
- await reader.cancel();
43340
- throw new Error(`Solana RPC response exceeds ${MAX_RPC_RESPONSE_BYTES} bytes.`);
43341
- }
43342
- chunks.push(next.value);
43343
- }
43344
- const bytes = new Uint8Array(length);
43345
- let offset = 0;
43346
- for (const chunk of chunks) {
43347
- bytes.set(chunk, offset);
43348
- offset += chunk.byteLength;
43349
- }
43350
- return JSON.parse(new TextDecoder().decode(bytes));
43351
- }
43352
- function rpcOptionsFrom(options) {
43353
- return {
43354
- allowPrivateNetwork: options.allowPrivateNetwork,
43355
- ...options.fetchImpl ? { fetchImpl: options.fetchImpl } : {},
43356
- ...options.lookup ? { lookup: options.lookup } : {}
43357
- };
43358
- }
43359
- function isRecord3(value) {
43360
- return typeof value === "object" && value !== null && !Array.isArray(value);
43361
- }
43362
-
43363
- // src/sweep.ts
43364
- var ERC20_ABI = [
43365
- {
43366
- type: "function",
43367
- name: "balanceOf",
43368
- stateMutability: "view",
43369
- inputs: [{ name: "account", type: "address" }],
43370
- outputs: [{ name: "balance", type: "uint256" }]
43371
- },
43372
- {
43373
- type: "function",
43374
- name: "transfer",
43375
- stateMutability: "nonpayable",
43376
- inputs: [
43377
- { name: "to", type: "address" },
43378
- { name: "amount", type: "uint256" }
43379
- ],
43380
- outputs: [{ name: "success", type: "bool" }]
43381
- }
43382
- ];
43383
- async function readUsdcBalance(args) {
43384
- if (isSolanaNetwork(args.network)) {
43385
- return await readSolanaUsdcBalance({
43386
- network: args.network,
43387
- configured: args.configured,
43388
- address: args.address,
43389
- allowPrivateNetwork: args.allowPrivateNetwork,
43390
- ...args.fetchImpl ? { fetchImpl: args.fetchImpl } : {},
43391
- ...args.lookup ? { lookup: args.lookup } : {}
43392
- });
43393
- }
43394
- const client = createNetworkPublicClient(args.network, args.configured, {
43395
- allowPrivateNetwork: args.allowPrivateNetwork,
43396
- ...args.fetchImpl ? { fetch: args.fetchImpl } : {},
43397
- ...args.lookup ? { lookup: args.lookup } : {}
43398
- });
43399
- return await client.readContract({
43400
- address: getAddress(
43401
- usesUsdcGas(args.network) ? getNetworkDefinition(args.network).usdc : args.configured.usdc
43402
- ),
43403
- abi: ERC20_ABI,
43404
- functionName: "balanceOf",
43405
- args: [getAddress(args.address)]
43406
- });
43407
- }
43408
-
43409
- // src/transfer.ts
43410
- import { randomBytes as randomBytes3, randomUUID } from "node:crypto";
43411
- import { mkdir as mkdir6, open as open6, stat as stat4, unlink as unlink4 } from "node:fs/promises";
43412
- import { dirname as dirname5 } from "node:path";
43413
-
43414
- // src/spend-policy.ts
43415
- import { randomBytes as randomBytes2 } from "node:crypto";
43416
- import { mkdir as mkdir4, open as open4, readFile as readFile3, rename as rename2, stat as stat2, unlink as unlink2, writeFile as writeFile2 } from "node:fs/promises";
43417
- import { dirname as dirname4 } from "node:path";
43418
- var ledgerSchema = external_exports.object({
43419
- date: external_exports.string().regex(/^\d{4}-\d{2}-\d{2}$/),
43420
- spentAtomic: external_exports.string().regex(/^\d+$/)
43421
- });
43422
- var spendReservationSchema = external_exports.object({
43423
- id: external_exports.string().min(1),
43424
- amountAtomic: external_exports.string().regex(/^\d+$/)
43425
- });
43426
- var ledgerRowSchema = ledgerSchema.extend({
43427
- wallet: walletNameSchema.optional(),
43428
- reservations: external_exports.array(spendReservationSchema).optional()
43429
- });
43430
- var ledgerFileSchema = external_exports.union([
43431
- external_exports.object({ version: external_exports.literal(1), rows: external_exports.array(ledgerRowSchema) }),
43432
- ledgerRowSchema
43433
- ]);
43434
- var SpendCapError = class extends Error {
43435
- constructor(code, message) {
43436
- super(message);
43437
- this.code = code;
43438
- this.name = "SpendCapError";
43439
- }
43440
- code;
43441
- };
43442
- function utcDateKey(now = /* @__PURE__ */ new Date()) {
43443
- return now.toISOString().slice(0, 10);
43444
- }
43445
- async function readStoredSpendLedgerRows(path, now) {
43446
- let raw;
43447
- try {
43448
- raw = await readFile3(path, "utf8");
43449
- } catch (error61) {
43450
- if (isMissingFile(error61)) return [];
43451
- throw error61;
43452
- }
43453
- const parsed = ledgerFileSchema.parse(JSON.parse(raw));
43454
- const rows = "rows" in parsed ? parsed.rows : [parsed];
43455
- const today = utcDateKey(now);
43456
- return rows.filter((row) => row.date === today).map((row) => ({
43457
- date: row.date,
43458
- spentAtomic: row.spentAtomic,
43459
- wallet: row.wallet ?? DEFAULT_WALLET_NAME,
43460
- ...row.reservations === void 0 ? {} : { reservations: row.reservations }
43461
- }));
43462
- }
43463
- async function reserveSpend(amountAtomic, caps, options) {
43464
- if (amountAtomic < 0n) {
43465
- throw new Error("Spend amount cannot be negative.");
43466
- }
43467
- const perCallAtomic = parseAtomicCap(caps.perCallAtomic, "per-call");
43468
- const perDayAtomic = parseAtomicCap(caps.perDayAtomic, "per-day");
43469
- if (amountAtomic > perCallAtomic) {
43470
- throw new SpendCapError(
43471
- "per_call_cap_exceeded",
43472
- `Payment quote ${amountAtomic} atomic USDC exceeds the per-call cap ${perCallAtomic}. Refusing to sign.`
43473
- );
43474
- }
43475
- const ledgerPath = options?.ledgerPath ?? getAgentCashPaths().ledger;
43476
- const now = options?.now ?? /* @__PURE__ */ new Date();
43477
- const wallet = options?.wallet ?? DEFAULT_WALLET_NAME;
43478
- return await withLedgerLock(ledgerPath, async () => {
43479
- const rows = await readStoredSpendLedgerRows(ledgerPath, now);
43480
- const current = rows.find((row) => row.wallet === wallet);
43481
- if (options?.reservationId !== void 0) {
43482
- if (current?.reservations?.some((reservation) => reservation.id === options.reservationId)) {
43483
- throw new Error(`Spend reservation ${options.reservationId} already exists.`);
43484
- }
43485
- }
43486
- const spentAtomic = current?.spentAtomic ?? "0";
43487
- const nextSpent = BigInt(spentAtomic) + amountAtomic;
43488
- if (nextSpent > perDayAtomic) {
43489
- throw new SpendCapError(
43490
- "per_day_cap_exceeded",
43491
- `Payment quote ${amountAtomic} atomic USDC would raise today's spend to ${nextSpent} for wallet ${wallet}, above the per-day cap ${perDayAtomic}. Refusing to sign.`
43492
- );
43493
- }
43494
- const nextRow = {
43495
- date: utcDateKey(now),
43496
- spentAtomic: nextSpent.toString(),
43497
- wallet,
43498
- ...(current?.reservations?.length ?? 0) === 0 && options?.reservationId === void 0 ? {} : {
43499
- reservations: [
43500
- ...current?.reservations ?? [],
43501
- ...options?.reservationId === void 0 ? [] : [{ id: options.reservationId, amountAtomic: amountAtomic.toString() }]
43502
- ]
43503
- }
43504
- };
43505
- const nextRows = [...rows.filter((row) => row.wallet !== wallet), nextRow].sort(
43506
- (a, b) => a.wallet.localeCompare(b.wallet)
43507
- );
43508
- await writeLedgerAtomically(ledgerPath, nextRows);
43509
- return { date: nextRow.date, spentAtomic: nextRow.spentAtomic };
43510
- });
43511
- }
43512
- async function releaseSpend(amountAtomic, options) {
43513
- if (amountAtomic < 0n) {
43514
- throw new Error("Spend amount cannot be negative.");
43515
- }
43516
- const ledgerPath = options.ledgerPath ?? getAgentCashPaths().ledger;
43517
- const now = options.now ?? /* @__PURE__ */ new Date();
43518
- const wallet = options.wallet ?? DEFAULT_WALLET_NAME;
43519
- return await withLedgerLock(ledgerPath, async () => {
43520
- const today = utcDateKey(now);
43521
- const rows = await readStoredSpendLedgerRows(ledgerPath, now);
43522
- const current = rows.find((row) => row.wallet === wallet);
43523
- if (options.reservedOn !== today) {
43524
- return { date: today, spentAtomic: current?.spentAtomic ?? "0" };
43525
- }
43526
- const reservation = options.reservationId === void 0 ? void 0 : current?.reservations?.find((candidate) => candidate.id === options.reservationId);
43527
- if (options.reservationId !== void 0 && reservation === void 0) {
43528
- return { date: today, spentAtomic: current?.spentAtomic ?? "0" };
43529
- }
43530
- if (reservation !== void 0 && reservation.amountAtomic !== amountAtomic.toString()) {
43531
- throw new Error(`Spend reservation ${options.reservationId} has a different amount.`);
43532
- }
43533
- const spentAtomic = BigInt(current?.spentAtomic ?? "0");
43534
- const releasedAmount = reservation === void 0 ? amountAtomic : BigInt(reservation.amountAtomic);
43535
- const nextSpent = spentAtomic > releasedAmount ? spentAtomic - releasedAmount : 0n;
43536
- const remainingReservations = current?.reservations?.filter(
43537
- (candidate) => candidate.id !== options.reservationId
43538
- );
43539
- const nextRow = {
43540
- date: today,
43541
- spentAtomic: nextSpent.toString(),
43542
- wallet,
43543
- ...remainingReservations === void 0 || remainingReservations.length === 0 ? {} : { reservations: remainingReservations }
43544
- };
43545
- const nextRows = [...rows.filter((row) => row.wallet !== wallet), nextRow].sort(
43546
- (a, b) => a.wallet.localeCompare(b.wallet)
43547
- );
43548
- await writeLedgerAtomically(ledgerPath, nextRows);
43549
- return { date: nextRow.date, spentAtomic: nextRow.spentAtomic };
43550
- });
43551
- }
43552
- function parseAtomicCap(value, label) {
43553
- if (!/^\d+$/.test(value)) {
43554
- throw new Error(`Invalid ${label} spend cap ${JSON.stringify(value)}.`);
43555
- }
43556
- return BigInt(value);
43557
- }
43558
- async function writeLedgerAtomically(path, rows) {
43559
- const ledger = rows.length === 1 && rows[0].wallet === DEFAULT_WALLET_NAME ? {
43560
- date: rows[0].date,
43561
- spentAtomic: rows[0].spentAtomic,
43562
- ...rows[0].reservations === void 0 ? {} : { reservations: rows[0].reservations }
43563
- } : { version: 1, rows };
43564
- await mkdir4(dirname4(path), { recursive: true, mode: 448 });
43565
- const temporaryPath = `${path}.${process.pid}.${randomBytes2(6).toString("hex")}.tmp`;
43566
- await writeFile2(temporaryPath, `${JSON.stringify(ledger, null, 2)}
43567
- `, {
43568
- encoding: "utf8",
43569
- mode: 384,
43570
- flag: "wx"
43571
- });
43572
- await rename2(temporaryPath, path);
43573
- }
43574
- async function withLedgerLock(path, operation) {
43575
- await mkdir4(dirname4(path), { recursive: true, mode: 448 });
43576
- const lockPath = `${path}.lock`;
43577
- const deadline = Date.now() + 2e3;
43578
- let handle;
43579
- while (!handle) {
43580
- try {
43581
- handle = await open4(lockPath, "wx", 384);
43582
- } catch (error61) {
43583
- if (!(error61 instanceof Error && "code" in error61 && error61.code === "EEXIST")) {
43584
- throw error61;
43585
- }
43586
- await removeStaleLock(lockPath);
43587
- if (Date.now() >= deadline) {
43588
- throw new Error("Timed out waiting for the spend ledger lock.");
43589
- }
43590
- await new Promise((resolve) => setTimeout(resolve, 20));
43591
- }
43592
- }
43593
- try {
43594
- return await operation();
43595
- } finally {
43596
- await handle.close();
43597
- await unlink2(lockPath).catch(() => void 0);
43598
- }
43599
- }
43600
- async function removeStaleLock(path) {
43601
- try {
43602
- const metadata = await stat2(path);
43603
- if (Date.now() - metadata.mtimeMs > 3e4) {
43604
- await unlink2(path);
43605
- }
43606
- } catch (error61) {
43607
- if (!isMissingFile(error61)) {
43608
- throw error61;
43609
- }
43610
- }
43611
- }
43612
-
43613
- // src/wallet-store.ts
43614
- import {
43615
- chmod as chmod2,
43616
- lstat,
43617
- mkdir as mkdir5,
43618
- open as open5,
43619
- readdir,
43620
- readFile as readFile4,
43621
- rename as rename3,
43622
- stat as stat3,
43623
- unlink as unlink3,
43624
- writeFile as writeFile3
43625
- } from "node:fs/promises";
43626
- var agentLinkSchema = external_exports.object({
43627
- apiBase: external_exports.string(),
43628
- clientId: external_exports.string(),
43629
- owner: external_exports.custom((value) => typeof value === "string" && value.startsWith("0x")),
43630
- label: external_exports.string(),
43631
- scopes: external_exports.array(external_exports.string()),
43632
- linkedAt: external_exports.iso.datetime(),
43633
- routerBaseUrl: external_exports.string().optional()
43634
- });
43635
- var ROUTER_TOPUP_TIERS = [1, 5, 20, 50];
43636
- var routerRefillSchema = external_exports.object({
43637
- belowUsd: external_exports.number().finite().min(0),
43638
- tierUsd: external_exports.union([external_exports.literal(1), external_exports.literal(5), external_exports.literal(20), external_exports.literal(50)])
43639
- });
43640
- var ceilingSweepPendingSchema = external_exports.strictObject({
43641
- owner: external_exports.string().regex(/^0x[0-9a-fA-F]{40}$/),
43642
- amountAtomic: external_exports.string().regex(/^\d+$/),
43643
- nonce: external_exports.string().regex(/^0x[0-9a-fA-F]{64}$/),
43644
- createdAt: external_exports.iso.datetime(),
43645
- status: external_exports.enum(["planned", "unknown"])
43646
- });
43647
- var walletEntrySchema = external_exports.object({
43648
- createdAt: external_exports.iso.datetime(),
43649
- label: external_exports.string().trim().min(1).max(80).optional(),
43650
- spendCaps: spendCapsSchema,
43651
- // On disk, null explicitly disables ceiling sweeps; an absent field is the
43652
- // backwards-compatible 5 USDC default and is not written during reads.
43653
- ceilingAtomic: external_exports.string().regex(/^\d+$/).nullable().optional(),
43654
- ceilingSweepAttemptedAt: external_exports.iso.datetime().optional(),
43655
- ceilingSweepPending: ceilingSweepPendingSchema.optional(),
43656
- routerRefill: routerRefillSchema.optional(),
43657
- link: agentLinkSchema.optional()
43658
- });
43659
- var walletRegistrySchema = external_exports.object({
43660
- version: external_exports.literal(1),
43661
- default: walletNameSchema.optional(),
43662
- wallets: external_exports.record(walletNameSchema, walletEntrySchema)
43663
- });
43664
- async function spendCapsForWallet(store, name = DEFAULT_WALLET_NAME) {
43665
- const entry = store.entry(name);
43666
- if (entry) return { ...entry.spendCaps };
43667
- return await readConfiguredSpendCaps(getVapiPaths(store.home).config);
43668
- }
43669
- async function readConfiguredSpendCaps(configPath) {
43670
- try {
43671
- const parsed = JSON.parse(await readFile4(configPath, "utf8"));
43672
- const caps = typeof parsed === "object" && parsed !== null ? Reflect.get(parsed, "spendCaps") : void 0;
43673
- const result = spendCapsSchema.safeParse(caps);
43674
- if (result.success) return result.data;
43675
- } catch {
43676
- }
43677
- return { ...DEFAULT_SPEND_CAPS };
43678
- }
43679
-
43680
44497
  // src/transfer.ts
43681
44498
  var TransferError = class extends Error {
43682
44499
  constructor(code, message, options = {}) {
@@ -43731,6 +44548,9 @@ async function transferBetweenAccounts(args) {
43731
44548
  throw transferError("not_linked");
43732
44549
  }
43733
44550
  const fromAddress = getAddress(rawFromAddress);
44551
+ if (args.expectedFromAddress !== void 0 && (!isAddress(args.expectedFromAddress, { strict: false }) || !sameAddress(fromAddress, args.expectedFromAddress))) {
44552
+ throw transferError("account_address_mismatch");
44553
+ }
43734
44554
  let siblingResult;
43735
44555
  try {
43736
44556
  siblingResult = await fetchSiblings({
@@ -43762,8 +44582,12 @@ async function transferBetweenAccounts(args) {
43762
44582
  owner: siblingResult.owner,
43763
44583
  siblings: siblingResult.siblings
43764
44584
  });
44585
+ if (args.expectedToAddress !== void 0 && (!isAddress(args.expectedToAddress, { strict: false }) || !sameAddress(recipient.address, args.expectedToAddress))) {
44586
+ throw transferError("account_address_mismatch");
44587
+ }
43765
44588
  const purpose = args.purpose ?? "send";
43766
- if (purpose === "sweep" && recipient.toKind !== "owner") {
44589
+ const isSweepTarget = recipient.toKind === "owner" || recipient.toKind === "account" && args.sweepParent !== void 0 && isAddress(args.sweepParent, { strict: false }) && getAddress(args.sweepParent) === getAddress(recipient.address);
44590
+ if (purpose === "sweep" && !isSweepTarget) {
43767
44591
  throw transferError("sweep_not_owner");
43768
44592
  }
43769
44593
  const home = args.home ?? args.store.home;
@@ -43784,7 +44608,7 @@ async function transferBetweenAccounts(args) {
43784
44608
  if (args.resume !== void 0) {
43785
44609
  return await resumeTransfer(context, args.resume);
43786
44610
  }
43787
- const nonce = args.nonce ?? bytesToHex(randomBytes3(32));
44611
+ const nonce = args.nonce ?? bytesToHex(randomBytes5(32));
43788
44612
  if (!NONCE_PATTERN.test(nonce)) {
43789
44613
  throw new TransferError(
43790
44614
  "authorization_invalid",
@@ -43806,7 +44630,9 @@ async function transferBetweenAccounts(args) {
43806
44630
  );
43807
44631
  reservedOn = reservation.date;
43808
44632
  } catch (error61) {
43809
- if (error61 instanceof SpendCapError) throw transferError(error61.code);
44633
+ if (error61 instanceof SpendCapError && isTransferErrorCode(error61.code)) {
44634
+ throw transferError(error61.code);
44635
+ }
43810
44636
  throw transferError("network_error");
43811
44637
  }
43812
44638
  }
@@ -44009,8 +44835,8 @@ async function postRelay(context, request, options) {
44009
44835
  `wallet:${context.args.from}`,
44010
44836
  async () => {
44011
44837
  await context.args.store.reload();
44012
- const link = context.args.store.entry(context.args.from)?.link;
44013
- if (link === void 0 || safeOrigin(link.apiBase) !== safeOrigin(context.args.apiBase)) {
44838
+ const link2 = context.args.store.entry(context.args.from)?.link;
44839
+ if (link2 === void 0 || safeOrigin(link2.apiBase) !== safeOrigin(context.args.apiBase)) {
44014
44840
  throw transferError("not_linked");
44015
44841
  }
44016
44842
  return await agentFetch(
@@ -44167,7 +44993,7 @@ async function findTransferReceipt(context, nonce) {
44167
44993
  return [...receipts].reverse().find((candidate) => candidate.transfer?.nonce.toLowerCase() === nonce.toLowerCase());
44168
44994
  }
44169
44995
  async function withTransferLock(context, nonce, operation) {
44170
- await mkdir6(dirname5(context.receiptsPath), { recursive: true, mode: 448 });
44996
+ await mkdir9(dirname6(context.receiptsPath), { recursive: true, mode: 448 });
44171
44997
  const lockPath = `${context.receiptsPath}.${context.args.from}.${nonce.toLowerCase()}.transfer.lock`;
44172
44998
  const requestTimeout = Math.max(0, context.args.timeoutMs ?? DEFAULT_TIMEOUT_MS);
44173
44999
  const deadline = Date.now() + Math.max(5e3, requestTimeout + 5e3);
@@ -44175,7 +45001,7 @@ async function withTransferLock(context, nonce, operation) {
44175
45001
  let handle;
44176
45002
  while (!handle) {
44177
45003
  try {
44178
- handle = await open6(lockPath, "wx", 384);
45004
+ handle = await open8(lockPath, "wx", 384);
44179
45005
  } catch (error61) {
44180
45006
  if (!(error61 instanceof Error && "code" in error61 && error61.code === "EEXIST")) throw error61;
44181
45007
  await removeStaleTransferLock(lockPath, staleAfterMs);
@@ -44189,13 +45015,13 @@ async function withTransferLock(context, nonce, operation) {
44189
45015
  return await operation();
44190
45016
  } finally {
44191
45017
  await handle.close();
44192
- await unlink4(lockPath).catch(() => void 0);
45018
+ await unlink6(lockPath).catch(() => void 0);
44193
45019
  }
44194
45020
  }
44195
45021
  async function removeStaleTransferLock(path, staleAfterMs) {
44196
45022
  try {
44197
- const metadata = await stat4(path);
44198
- if (Date.now() - metadata.mtimeMs > staleAfterMs) await unlink4(path);
45023
+ const metadata = await stat7(path);
45024
+ if (Date.now() - metadata.mtimeMs > staleAfterMs) await unlink6(path);
44199
45025
  } catch (error61) {
44200
45026
  if (!(error61 instanceof Error && "code" in error61 && error61.code === "ENOENT")) throw error61;
44201
45027
  }
@@ -44349,6 +45175,7 @@ function isTransferErrorCode(value) {
44349
45175
  "resume_not_found",
44350
45176
  "resume_mismatch",
44351
45177
  "signer_mismatch",
45178
+ "account_address_mismatch",
44352
45179
  "signing_failed"
44353
45180
  ].includes(value);
44354
45181
  }
@@ -44360,7 +45187,7 @@ function transferError(code, options = {}) {
44360
45187
  unsupported: "This vAPI server does not relay transfers yet. No money moved.",
44361
45188
  recipient_not_allowed: "The recipient is not the owner or an active sibling account. No money moved.",
44362
45189
  ambiguous_recipient: "The sibling account name is ambiguous. No money moved.",
44363
- sweep_not_owner: "A sweep can only send funds to the owner. No money moved.",
45190
+ sweep_not_owner: "A sweep can only send funds to the owner or its treasury. No money moved.",
44364
45191
  per_call_cap_exceeded: "The amount exceeds the sending account's per-call cap. No money moved.",
44365
45192
  per_day_cap_exceeded: "The amount exceeds the sending account's remaining daily cap. No money moved.",
44366
45193
  payer_mismatch: "The relay rejected the sending account. No money moved.",
@@ -44373,17 +45200,644 @@ function transferError(code, options = {}) {
44373
45200
  resume_not_found: "No resumable transfer was found for that nonce. No money moved.",
44374
45201
  resume_mismatch: "The resumed transfer does not match the original request. No money moved.",
44375
45202
  signer_mismatch: "The unlocked signer does not match the sending account. No money moved.",
45203
+ account_address_mismatch: "The sending or recipient account no longer matches the address recorded by its movement. No money moved.",
44376
45204
  signing_failed: "The transfer authorization could not be signed. No money moved."
44377
45205
  };
44378
45206
  return new TransferError(code, messages[code], options);
44379
45207
  }
44380
45208
 
45209
+ // src/movement.ts
45210
+ var movementIdSchema = external_exports.string().regex(/^mv_[A-Za-z0-9_-]{8,128}$/);
45211
+ var nonceSchema = external_exports.string().regex(/^0x[0-9a-fA-F]{64}$/);
45212
+ var amountSchema = external_exports.string().regex(/^\d+\.\d{2}$/);
45213
+ var movementReasonSchema = external_exports.enum([
45214
+ "distribute",
45215
+ "allocate",
45216
+ "rebalance",
45217
+ "delegate",
45218
+ "sweep",
45219
+ "send"
45220
+ ]);
45221
+ var movementPurposeSchema = external_exports.enum(["send", "sweep"]);
45222
+ var movementAddressSchema = external_exports.string().refine((value) => isAddress(value, { strict: false }));
45223
+ var movementLegSchema = external_exports.strictObject({
45224
+ from: walletNameSchema,
45225
+ to: external_exports.string().trim().min(1),
45226
+ fromAddress: movementAddressSchema.optional(),
45227
+ toAddress: movementAddressSchema.optional(),
45228
+ amountUsd: amountSchema,
45229
+ purpose: movementPurposeSchema,
45230
+ nonce: nonceSchema,
45231
+ status: external_exports.enum(["planned", "sent", "failed", "unknown", "cancelled"]),
45232
+ txHash: external_exports.string().regex(/^0x[0-9a-fA-F]+$/).optional(),
45233
+ reason: external_exports.string().min(1).optional(),
45234
+ retryable: external_exports.boolean().optional(),
45235
+ restored: external_exports.literal(true).optional(),
45236
+ addressBindingSource: external_exports.enum(["journal", "terminal-review"]).optional()
45237
+ });
45238
+ var movementSchema = external_exports.strictObject({
45239
+ v: external_exports.literal(2),
45240
+ id: movementIdSchema,
45241
+ reason: movementReasonSchema,
45242
+ from: walletNameSchema,
45243
+ // The only account a `sweep` leg may name besides the owner. Sweeps skip the
45244
+ // spend caps, so the parent comes from the plan, never from a leg's `to`.
45245
+ treasury: walletNameSchema.optional(),
45246
+ network: external_exports.enum([BASE_MAINNET_CAIP2, ARC_MAINNET_CAIP2]),
45247
+ createdAt: external_exports.iso.datetime(),
45248
+ legs: external_exports.array(movementLegSchema).min(1)
45249
+ });
45250
+ var legacyMovementSchema = external_exports.strictObject({
45251
+ v: external_exports.literal(1),
45252
+ id: movementIdSchema,
45253
+ reason: external_exports.literal("distribute"),
45254
+ from: walletNameSchema,
45255
+ network: external_exports.enum([BASE_MAINNET_CAIP2, ARC_MAINNET_CAIP2]),
45256
+ createdAt: external_exports.iso.datetime(),
45257
+ legs: external_exports.array(
45258
+ external_exports.strictObject({
45259
+ to: external_exports.string().trim().min(1),
45260
+ amountUsd: amountSchema,
45261
+ nonce: nonceSchema,
45262
+ status: external_exports.enum(["planned", "sent", "failed", "unknown"]),
45263
+ txHash: external_exports.string().regex(/^0x[0-9a-fA-F]+$/).optional(),
45264
+ reason: external_exports.string().min(1).optional(),
45265
+ retryable: external_exports.boolean().optional()
45266
+ })
45267
+ ).min(1)
45268
+ });
45269
+ var MovementError = class extends Error {
45270
+ constructor(code, message, movementId) {
45271
+ super(message);
45272
+ this.code = code;
45273
+ this.movementId = movementId;
45274
+ this.name = "MovementError";
45275
+ }
45276
+ code;
45277
+ movementId;
45278
+ };
45279
+ async function listUnfinishedMovements(options) {
45280
+ const directory = movementsDirectory(options.home);
45281
+ let names;
45282
+ try {
45283
+ names = await readdir3(directory);
45284
+ } catch (error61) {
45285
+ if (isMissingFile(error61)) return [];
45286
+ throw error61;
45287
+ }
45288
+ const summaries = [];
45289
+ for (const name of names.filter((candidate) => candidate.endsWith(".json")).sort()) {
45290
+ const movement = parseMovement(JSON.parse(await readFile9(join5(directory, name), "utf8")));
45291
+ if (options.from !== void 0 && !movement.legs.some((leg) => leg.from === options.from)) {
45292
+ continue;
45293
+ }
45294
+ const pendingLegs = movement.legs.filter((leg) => leg.status === "planned").length;
45295
+ const unknownLegs = movement.legs.filter((leg) => leg.status === "unknown").length;
45296
+ if (!await movementHasOpenLeg(options.home, movement, options.from)) continue;
45297
+ summaries.push({
45298
+ id: movement.id,
45299
+ from: movement.from,
45300
+ network: movement.network,
45301
+ createdAt: movement.createdAt,
45302
+ pendingLegs,
45303
+ unknownLegs
45304
+ });
45305
+ }
45306
+ return summaries.sort(
45307
+ (left, right) => left.createdAt.localeCompare(right.createdAt) || left.id.localeCompare(right.id)
45308
+ );
45309
+ }
45310
+ async function movementHasOpenLeg(home, movement, from15) {
45311
+ for (const leg of movement.legs) {
45312
+ if (from15 !== void 0 && leg.from !== from15) continue;
45313
+ if (isOpenMovementLeg(leg, "unsigned")) return true;
45314
+ if (leg.status !== "failed") continue;
45315
+ const journal = await readTransferJournal(home, leg.from, leg.nonce);
45316
+ if (isOpenMovementLeg(leg, journal.signed ? "signed" : "unsigned")) return true;
45317
+ }
45318
+ return false;
45319
+ }
45320
+ async function readMovement(home, id) {
45321
+ const safeId = parseMovementId(id);
45322
+ try {
45323
+ return parseMovement(
45324
+ JSON.parse(await readFile9(join5(movementsDirectory(home), `${safeId}.json`), "utf8"))
45325
+ );
45326
+ } catch (error61) {
45327
+ if (isMissingFile(error61)) {
45328
+ throw new MovementError(
45329
+ "movement_not_found",
45330
+ `No movement named ${safeId} was found.`,
45331
+ safeId
45332
+ );
45333
+ }
45334
+ throw error61;
45335
+ }
45336
+ }
45337
+ async function reconcileUnresolvedMovementsForAccountLocked(home, account, deps) {
45338
+ for (const summary of await listUnfinishedMovements({ home, from: account })) {
45339
+ let movement = await readMovement(home, summary.id);
45340
+ try {
45341
+ for (let index2 = 0; index2 < movement.legs.length; index2 += 1) {
45342
+ const leg = movement.legs[index2];
45343
+ if (leg?.from !== account || leg.status !== "planned" && !(leg.status === "failed" && leg.retryable === true)) {
45344
+ continue;
45345
+ }
45346
+ const reconciled = await reconcileJournaledLeg(movement, index2, home);
45347
+ if (reconciled !== movement) {
45348
+ movement = reconciled;
45349
+ await writeMovement(home, movement);
45350
+ }
45351
+ }
45352
+ for (let index2 = 0; index2 < movement.legs.length; index2 += 1) {
45353
+ const leg = movement.legs[index2];
45354
+ if (leg?.from !== account || leg.status !== "unknown") continue;
45355
+ movement = await resolveUnknownLeg(movement, index2, home, deps);
45356
+ }
45357
+ for (let index2 = 0; index2 < movement.legs.length; index2 += 1) {
45358
+ const leg = movement.legs[index2];
45359
+ if (leg?.from !== account || leg.status !== "failed") continue;
45360
+ movement = await prepareFailedLeg(movement, index2, home, deps);
45361
+ }
45362
+ } catch {
45363
+ }
45364
+ if (await movementHasOpenLeg(home, movement, account)) return movement;
45365
+ }
45366
+ return void 0;
45367
+ }
45368
+ async function resolveUnknownLeg(movement, index2, home, deps) {
45369
+ const leg = movement.legs[index2];
45370
+ if (isExpiredRestoredReview(leg)) {
45371
+ if (deps.replaceExpiredRestored !== true) {
45372
+ await throwRestoredLegReview(movement, leg, deps);
45373
+ }
45374
+ const journal2 = await readTransferJournal(home, leg.from, leg.nonce);
45375
+ await releaseExpiredReservation(leg, journal2, home, deps);
45376
+ const bound = leg.fromAddress === void 0 || leg.toAddress === void 0 ? await bindLegAddresses(leg, deps) : leg;
45377
+ movement = replaceLeg(
45378
+ movement,
45379
+ index2,
45380
+ plannedLeg(bound, (deps.randomNonce ?? createMovementNonce)())
45381
+ );
45382
+ await writeMovement(home, movement);
45383
+ return movement;
45384
+ }
45385
+ try {
45386
+ const result = await callTransfer(movement, leg, deps, true);
45387
+ if (result.status === "sent") {
45388
+ movement = replaceLeg(movement, index2, sentLeg(leg, result.txHash));
45389
+ await writeMovement(home, movement);
45390
+ return movement;
45391
+ }
45392
+ } catch (error61) {
45393
+ if (error61 instanceof TransferError || error61 instanceof SweepParentUnknownError) {
45394
+ } else {
45395
+ await reconcileCrash(movement, index2, home);
45396
+ throw error61;
45397
+ }
45398
+ }
45399
+ const journal = await readTransferJournal(home, leg.from, leg.nonce);
45400
+ const state = await authorizationState(movement, leg, journal.validBefore, home, deps);
45401
+ if (state === "settled") {
45402
+ movement = replaceLeg(movement, index2, sentLeg(leg));
45403
+ } else if (state === "expired") {
45404
+ if (leg.restored === true && deps.replaceExpiredRestored !== true) {
45405
+ movement = replaceLeg(movement, index2, restoredExpiredReviewLeg(leg));
45406
+ await writeMovement(home, movement);
45407
+ await throwRestoredLegReview(movement, movement.legs[index2], deps);
45408
+ }
45409
+ await releaseExpiredReservation(leg, journal, home, deps);
45410
+ movement = replaceLeg(
45411
+ movement,
45412
+ index2,
45413
+ plannedLeg(leg, (deps.randomNonce ?? createMovementNonce)())
45414
+ );
45415
+ }
45416
+ await writeMovement(home, movement);
45417
+ return movement;
45418
+ }
45419
+ async function reconcileJournaledLeg(movement, index2, home) {
45420
+ const leg = movement.legs[index2];
45421
+ const journal = await readTransferJournal(home, leg.from, leg.nonce);
45422
+ if (journal.latest?.transfer?.status === "sent") {
45423
+ return replaceLeg(
45424
+ movement,
45425
+ index2,
45426
+ sentLeg(
45427
+ leg,
45428
+ journal.latest.transfer.txHash === null ? void 0 : journal.latest.transfer.txHash
45429
+ )
45430
+ );
45431
+ }
45432
+ return journal.signed ? replaceLeg(movement, index2, unknownLeg(leg)) : movement;
45433
+ }
45434
+ async function prepareFailedLeg(movement, index2, home, deps) {
45435
+ const leg = movement.legs[index2];
45436
+ if (leg.retryable === true) {
45437
+ const reconciled = await reconcileJournaledLeg(movement, index2, home);
45438
+ if (reconciled !== movement) {
45439
+ await writeMovement(home, reconciled);
45440
+ return reconciled;
45441
+ }
45442
+ return replaceLeg(movement, index2, plannedLeg(leg, leg.nonce, true));
45443
+ }
45444
+ const journal = await readTransferJournal(home, leg.from, leg.nonce);
45445
+ if (!journal.signed) return movement;
45446
+ const state = await authorizationState(movement, leg, journal.validBefore, home, deps);
45447
+ if (state === "settled") {
45448
+ movement = replaceLeg(movement, index2, sentLeg(leg));
45449
+ } else if (state === "expired") {
45450
+ if (leg.restored === true && deps.replaceExpiredRestored !== true) {
45451
+ movement = replaceLeg(movement, index2, restoredExpiredReviewLeg(leg));
45452
+ await writeMovement(home, movement);
45453
+ await throwRestoredLegReview(movement, movement.legs[index2], deps);
45454
+ }
45455
+ await releaseExpiredReservation(leg, journal, home, deps);
45456
+ movement = replaceLeg(
45457
+ movement,
45458
+ index2,
45459
+ plannedLeg(leg, (deps.randomNonce ?? createMovementNonce)())
45460
+ );
45461
+ }
45462
+ await writeMovement(home, movement);
45463
+ return movement;
45464
+ }
45465
+ async function callTransfer(movement, leg, deps, resume) {
45466
+ if (leg.fromAddress !== void 0 && leg.toAddress !== void 0) {
45467
+ await assertLegAddressBinding(movement, leg, deps);
45468
+ } else if (!resume) {
45469
+ throw movementAddressUnboundError(movement);
45470
+ }
45471
+ const transfer = deps.transfer ?? transferBetweenAccounts;
45472
+ const sweepParent = await readSweepParent(movement, leg, deps);
45473
+ return await transfer({
45474
+ store: deps.store,
45475
+ secrets: deps.secrets,
45476
+ apiBase: deps.apiBase,
45477
+ from: leg.from,
45478
+ to: leg.to,
45479
+ amountUsd: leg.amountUsd,
45480
+ network: movement.network,
45481
+ purpose: leg.purpose,
45482
+ expectedFromAddress: leg.fromAddress,
45483
+ expectedToAddress: leg.toAddress,
45484
+ ...sweepParent === void 0 ? {} : { sweepParent },
45485
+ home: deps.home ?? deps.store.home,
45486
+ ...resume ? { resume: leg.nonce } : { nonce: leg.nonce },
45487
+ ...deps.unlock === void 0 ? {} : { unlock: deps.unlock },
45488
+ ...deps.fetchImpl === void 0 ? {} : { fetchImpl: deps.fetchImpl },
45489
+ ...deps.now === void 0 ? {} : { now: deps.now },
45490
+ ...deps.timeoutMs === void 0 ? {} : { timeoutMs: deps.timeoutMs },
45491
+ ...deps.ledgerPath === void 0 ? {} : { ledgerPath: deps.ledgerPath }
45492
+ });
45493
+ }
45494
+ async function reconcileCrash(movement, index2, home) {
45495
+ const leg = movement.legs[index2];
45496
+ const journal = await readTransferJournal(home, leg.from, leg.nonce);
45497
+ let reconciled = leg;
45498
+ if (journal.latest?.transfer?.status === "sent") {
45499
+ reconciled = sentLeg(
45500
+ leg,
45501
+ journal.latest.transfer.txHash === null ? void 0 : journal.latest.transfer.txHash
45502
+ );
45503
+ } else if (journal.latest?.transfer?.status === "failed") {
45504
+ reconciled = journalFailedLeg(leg, journal.latest.error?.code ?? "transfer_failed");
45505
+ } else if (journal.signed) {
45506
+ reconciled = unknownLeg(leg);
45507
+ }
45508
+ await writeMovement(home, replaceLeg(movement, index2, reconciled));
45509
+ }
45510
+ async function authorizationState(movement, leg, validBefore, home, deps) {
45511
+ if (deps.authorizationState !== void 0) {
45512
+ return await deps.authorizationState({
45513
+ movement,
45514
+ leg,
45515
+ ...validBefore === void 0 ? {} : { validBefore }
45516
+ });
45517
+ }
45518
+ const journal = await readTransferJournal(home, leg.from, leg.nonce);
45519
+ const receipt = journal.request;
45520
+ const request = receipt?.transfer?.request;
45521
+ if (receipt === void 0 || request === void 0) {
45522
+ throw new MovementError(
45523
+ "invalid_movement",
45524
+ `Movement ${movement.id} has no journaled authorization for nonce ${leg.nonce}.`,
45525
+ movement.id
45526
+ );
45527
+ }
45528
+ const config2 = await loadConfig(getVapiPaths(home).config);
45529
+ const settlementReceipt = {
45530
+ ...receipt,
45531
+ authorization: {
45532
+ from: request.authorization.from,
45533
+ nonce: request.authorization.nonce,
45534
+ validBefore: request.authorization.validBefore
45535
+ }
45536
+ };
45537
+ return (await checkReceiptSettlement(settlementReceipt, config2, {
45538
+ ...deps.fetchImpl === void 0 ? {} : { fetchImpl: deps.fetchImpl }
45539
+ })).state;
45540
+ }
45541
+ async function releaseExpiredReservation(leg, journal, home, deps) {
45542
+ if (journal.reservedOn === void 0) return;
45543
+ await releaseSpend(parseUsdCents(leg.amountUsd) * 10000n, {
45544
+ reservedOn: journal.reservedOn,
45545
+ ledgerPath: deps.ledgerPath ?? getVapiPaths(home).ledger,
45546
+ now: new Date((deps.now ?? Date.now)()),
45547
+ wallet: leg.from,
45548
+ reservationId: leg.nonce.toLowerCase()
45549
+ });
45550
+ }
45551
+ function sentLeg(leg, txHash) {
45552
+ return {
45553
+ from: leg.from,
45554
+ to: leg.to,
45555
+ ...legAddressBinding(leg),
45556
+ amountUsd: leg.amountUsd,
45557
+ purpose: leg.purpose,
45558
+ nonce: leg.nonce,
45559
+ status: "sent",
45560
+ ...restoredMarker(leg),
45561
+ ...txHash === void 0 || txHash === null ? {} : { txHash }
45562
+ };
45563
+ }
45564
+ function unknownLeg(leg) {
45565
+ return {
45566
+ from: leg.from,
45567
+ to: leg.to,
45568
+ ...legAddressBinding(leg),
45569
+ amountUsd: leg.amountUsd,
45570
+ purpose: leg.purpose,
45571
+ nonce: leg.nonce,
45572
+ status: "unknown",
45573
+ ...restoredMarker(leg)
45574
+ };
45575
+ }
45576
+ function journalFailedLeg(leg, reason) {
45577
+ return {
45578
+ from: leg.from,
45579
+ to: leg.to,
45580
+ ...legAddressBinding(leg),
45581
+ amountUsd: leg.amountUsd,
45582
+ purpose: leg.purpose,
45583
+ nonce: leg.nonce,
45584
+ status: "failed",
45585
+ reason,
45586
+ ...restoredMarker(leg)
45587
+ };
45588
+ }
45589
+ function plannedLeg(leg, nonce, preserveRestored = false) {
45590
+ return {
45591
+ from: leg.from,
45592
+ to: leg.to,
45593
+ ...legAddressBinding(leg),
45594
+ amountUsd: leg.amountUsd,
45595
+ purpose: leg.purpose,
45596
+ nonce,
45597
+ status: "planned",
45598
+ ...preserveRestored ? restoredMarker(leg) : {}
45599
+ };
45600
+ }
45601
+ function restoredExpiredReviewLeg(leg) {
45602
+ return {
45603
+ from: leg.from,
45604
+ to: leg.to,
45605
+ ...legAddressBinding(leg),
45606
+ amountUsd: leg.amountUsd,
45607
+ purpose: leg.purpose,
45608
+ nonce: leg.nonce,
45609
+ status: "unknown",
45610
+ reason: "restored_nonce_expired",
45611
+ restored: true
45612
+ };
45613
+ }
45614
+ function restoredMarker(leg) {
45615
+ return leg.restored === true ? { restored: true } : {};
45616
+ }
45617
+ function legAddressBinding(leg) {
45618
+ return {
45619
+ ...leg.fromAddress === void 0 ? {} : { fromAddress: leg.fromAddress },
45620
+ ...leg.toAddress === void 0 ? {} : { toAddress: leg.toAddress },
45621
+ ...leg.addressBindingSource === void 0 ? {} : { addressBindingSource: leg.addressBindingSource }
45622
+ };
45623
+ }
45624
+ function isExpiredRestoredReview(leg) {
45625
+ return leg.restored === true && leg.reason === "restored_nonce_expired";
45626
+ }
45627
+ async function throwRestoredLegReview(movement, leg, deps, kind = "expired") {
45628
+ const address2 = await (deps.addressReader === void 0 ? deps.store.readAddress(leg.from) : deps.addressReader(leg.from));
45629
+ const explorer = address2 === void 0 ? void 0 : explorerAddressUrl(movement.network, address2);
45630
+ const addressHint = address2 ?? `account ${leg.from}`;
45631
+ const explorerHint = explorer === void 0 ? "a block explorer" : explorer;
45632
+ throw new MovementError(
45633
+ "restored_leg_review",
45634
+ `Movement ${movement.id} restored a ${leg.amountUsd} USDC transfer from ${leg.from} to ${leg.to}, ${kind === "expired" ? `but its saved authorization ${leg.nonce} has expired` : "that the backup recorded before it was signed"}. The original device may already have paid it with a later nonce, so nothing was signed. Check the balance with vapi balance --account ${leg.from}, then inspect ${addressHint} on ${explorerHint}. If the transfer was not paid, continue deliberately in a terminal with vapi accounts distribute --resume ${movement.id} --replace-expired-restored. This override is unavailable through MCP.`,
45635
+ movement.id
45636
+ );
45637
+ }
45638
+ function replaceLeg(movement, index2, leg) {
45639
+ const legs = [...movement.legs];
45640
+ legs[index2] = movementLegSchema.parse(leg);
45641
+ return movementSchema.parse({ ...movement, legs });
45642
+ }
45643
+ function parseMovement(value) {
45644
+ const current = movementSchema.safeParse(value);
45645
+ if (current.success) return current.data;
45646
+ const legacy = legacyMovementSchema.parse(value);
45647
+ return movementSchema.parse({
45648
+ ...legacy,
45649
+ v: 2,
45650
+ legs: legacy.legs.map((leg) => ({
45651
+ ...leg,
45652
+ from: legacy.from,
45653
+ purpose: "send"
45654
+ }))
45655
+ });
45656
+ }
45657
+ async function bindLegAddresses(leg, deps) {
45658
+ const fromAddress = await resolveAccountAddress(leg.from, deps);
45659
+ if (fromAddress === void 0) {
45660
+ throw new MovementError(
45661
+ "invalid_movement",
45662
+ `Cannot plan a movement from ${leg.from}: its local address is unavailable. Restore that account, then retry.`
45663
+ );
45664
+ }
45665
+ const toAddress = await resolveRecipientAddress(leg.from, leg.to, deps);
45666
+ if (toAddress === void 0) {
45667
+ throw new MovementError(
45668
+ "invalid_movement",
45669
+ `Cannot plan a movement to ${leg.to}: its address is unavailable. Restore or relink that account, then retry.`
45670
+ );
45671
+ }
45672
+ return { ...leg, fromAddress, toAddress };
45673
+ }
45674
+ async function assertLegAddressBinding(movement, leg, deps) {
45675
+ if (leg.fromAddress === void 0 || leg.toAddress === void 0) {
45676
+ throw movementAddressUnboundError(movement);
45677
+ }
45678
+ const fromAddress = await resolveAccountAddress(leg.from, deps);
45679
+ if (fromAddress === void 0 || !sameAddress2(fromAddress, leg.fromAddress)) {
45680
+ throw new MovementError(
45681
+ "account_address_mismatch",
45682
+ `Movement ${movement.id} bound sender ${leg.from} to ${leg.fromAddress}, but that name now resolves differently. Restore ${leg.from} to the recorded address before resuming, or wait for any signed authorization to expire and run vapi accounts distribute --cancel ${movement.id}.`,
45683
+ movement.id
45684
+ );
45685
+ }
45686
+ const toAddress = await resolveRecipientAddress(leg.from, leg.to, deps);
45687
+ if (toAddress === void 0 || !sameAddress2(toAddress, leg.toAddress)) {
45688
+ throw new MovementError(
45689
+ "account_address_mismatch",
45690
+ `Movement ${movement.id} bound recipient ${leg.to} to ${leg.toAddress}, but that name now resolves differently. Restore or relink ${leg.to} to the recorded address before resuming, or wait for any signed authorization to expire and run vapi accounts distribute --cancel ${movement.id}.`,
45691
+ movement.id
45692
+ );
45693
+ }
45694
+ }
45695
+ function movementAddressUnboundError(movement) {
45696
+ return new MovementError(
45697
+ "movement_address_unbound",
45698
+ `Movement ${movement.id} predates address binding, so vAPI cannot safely sign its planned leg. Check balances and the saved nonce, then create a new movement deliberately if no authorization can settle.`,
45699
+ movement.id
45700
+ );
45701
+ }
45702
+ async function resolveAccountAddress(account, deps) {
45703
+ const raw = await (deps.addressReader ?? ((name) => deps.store.readAddress(name)))(account);
45704
+ return raw !== void 0 && isAddress(raw, { strict: false }) ? getAddress(raw) : void 0;
45705
+ }
45706
+ async function resolveRecipientAddress(from15, recipient, deps) {
45707
+ const requested = recipient.trim();
45708
+ if (isAddress(requested, { strict: false })) return getAddress(requested);
45709
+ if (requested === "owner") {
45710
+ const owner = deps.store.entry(from15)?.link?.owner;
45711
+ if (owner !== void 0 && isAddress(owner, { strict: false })) return getAddress(owner);
45712
+ } else {
45713
+ const account = walletNameSchema.safeParse(requested);
45714
+ if (account.success) {
45715
+ const local = await resolveAccountAddress(account.data, deps);
45716
+ if (local !== void 0) return local;
45717
+ }
45718
+ }
45719
+ const resolved = await deps.recipientAddressReader?.(from15, requested);
45720
+ return resolved !== void 0 && isAddress(resolved, { strict: false }) ? getAddress(resolved) : void 0;
45721
+ }
45722
+ function sameAddress2(left, right) {
45723
+ return isAddress(left, { strict: false }) && isAddress(right, { strict: false }) && getAddress(left) === getAddress(right);
45724
+ }
45725
+ var SweepParentUnknownError = class extends Error {
45726
+ };
45727
+ function isOwnerSweep(to) {
45728
+ return to.trim() === "owner" || isAddress(to, { strict: false });
45729
+ }
45730
+ async function readSweepParent(movement, leg, deps) {
45731
+ if (leg.purpose !== "sweep" || isOwnerSweep(leg.to)) return void 0;
45732
+ if (movement.treasury === void 0 || leg.to !== movement.treasury) {
45733
+ throw new SweepParentUnknownError();
45734
+ }
45735
+ const account = walletNameSchema.safeParse(leg.to);
45736
+ if (!account.success) throw new SweepParentUnknownError();
45737
+ const address2 = deps.addressReader === void 0 ? await deps.store.readAddress(account.data) : await deps.addressReader(account.data);
45738
+ if (address2 === void 0 || !isAddress(address2, { strict: false })) {
45739
+ throw new SweepParentUnknownError();
45740
+ }
45741
+ return address2;
45742
+ }
45743
+ function parseMovementId(value) {
45744
+ const parsed = movementIdSchema.safeParse(value);
45745
+ if (!parsed.success) throw new MovementError("invalid_movement", "The movement id is invalid.");
45746
+ return parsed.data;
45747
+ }
45748
+ function movementsDirectory(home) {
45749
+ return join5(home, "movements");
45750
+ }
45751
+ function createMovementNonce() {
45752
+ return bytesToHex(randomBytes6(32));
45753
+ }
45754
+ async function writeMovement(home, movement) {
45755
+ const parsed = movementSchema.parse(movement);
45756
+ const directory = movementsDirectory(home);
45757
+ await mkdir10(directory, { recursive: true, mode: 448 });
45758
+ await chmod3(directory, 448);
45759
+ const path = join5(directory, `${parsed.id}.json`);
45760
+ const temporary = join5(
45761
+ directory,
45762
+ `.${parsed.id}.${process.pid}.${randomBytes6(6).toString("hex")}.tmp`
45763
+ );
45764
+ let handle;
45765
+ try {
45766
+ handle = await open9(temporary, "wx", 384);
45767
+ await handle.writeFile(serializeMovement(parsed), "utf8");
45768
+ await handle.sync();
45769
+ await handle.close();
45770
+ handle = void 0;
45771
+ await rename6(temporary, path);
45772
+ const directoryHandle = await open9(directory, "r");
45773
+ try {
45774
+ await directoryHandle.sync();
45775
+ } finally {
45776
+ await directoryHandle.close();
45777
+ }
45778
+ } catch (error61) {
45779
+ await handle?.close().catch(() => void 0);
45780
+ await unlink7(temporary).catch(() => void 0);
45781
+ throw error61;
45782
+ }
45783
+ }
45784
+ function serializeMovement(movement) {
45785
+ return `${JSON.stringify(movementSchema.parse(movement), null, 2)}
45786
+ `;
45787
+ }
45788
+
45789
+ // src/sweep.ts
45790
+ var ERC20_ABI = [
45791
+ {
45792
+ type: "function",
45793
+ name: "balanceOf",
45794
+ stateMutability: "view",
45795
+ inputs: [{ name: "account", type: "address" }],
45796
+ outputs: [{ name: "balance", type: "uint256" }]
45797
+ },
45798
+ {
45799
+ type: "function",
45800
+ name: "transfer",
45801
+ stateMutability: "nonpayable",
45802
+ inputs: [
45803
+ { name: "to", type: "address" },
45804
+ { name: "amount", type: "uint256" }
45805
+ ],
45806
+ outputs: [{ name: "success", type: "bool" }]
45807
+ }
45808
+ ];
45809
+ async function readUsdcBalance(args) {
45810
+ if (isSolanaNetwork(args.network)) {
45811
+ return await readSolanaUsdcBalance({
45812
+ network: args.network,
45813
+ configured: args.configured,
45814
+ address: args.address,
45815
+ allowPrivateNetwork: args.allowPrivateNetwork,
45816
+ ...args.fetchImpl ? { fetchImpl: args.fetchImpl } : {},
45817
+ ...args.lookup ? { lookup: args.lookup } : {}
45818
+ });
45819
+ }
45820
+ const client = createNetworkPublicClient(args.network, args.configured, {
45821
+ allowPrivateNetwork: args.allowPrivateNetwork,
45822
+ ...args.fetchImpl ? { fetch: args.fetchImpl } : {},
45823
+ ...args.lookup ? { lookup: args.lookup } : {}
45824
+ });
45825
+ return await client.readContract({
45826
+ address: getAddress(
45827
+ usesUsdcGas(args.network) ? getNetworkDefinition(args.network).usdc : args.configured.usdc
45828
+ ),
45829
+ abi: ERC20_ABI,
45830
+ functionName: "balanceOf",
45831
+ args: [getAddress(args.address)]
45832
+ });
45833
+ }
45834
+
44381
45835
  // src/ceiling.ts
44382
45836
  var CEILING_SWEEP_MINIMUM_ATOMIC = 100000n;
44383
45837
  var CEILING_SWEEP_GUARD_MS = 10 * 60 * 1e3;
44384
45838
  var CEILING_SWEEP_TIMEOUT_MS = 1e4;
44385
45839
  function sweepTarget(account) {
44386
- return account.owner;
45840
+ return account.parent?.address ?? account.owner;
44387
45841
  }
44388
45842
  async function sweepAboveCeiling(args) {
44389
45843
  try {
@@ -44410,6 +45864,25 @@ async function sweepAboveCeilingLocked(args) {
44410
45864
  if (caps.effectiveCeilingAtomic === null) {
44411
45865
  return skipped(args.account, "ceiling_off");
44412
45866
  }
45867
+ const unresolvedMovement = await reconcileUnresolvedMovementsForAccountLocked(
45868
+ auditHome,
45869
+ args.account,
45870
+ {
45871
+ store: args.store,
45872
+ secrets: args.secrets,
45873
+ apiBase: args.apiBase,
45874
+ home: auditHome,
45875
+ ...args.transfer === void 0 ? {} : { transfer: args.transfer },
45876
+ ...args.addressReader === void 0 ? {} : { addressReader: args.addressReader },
45877
+ ...args.fetchImpl === void 0 ? {} : { fetchImpl: args.fetchImpl },
45878
+ ...args.now === void 0 ? {} : { now: args.now },
45879
+ ...args.timeoutMs === void 0 ? {} : { timeoutMs: args.timeoutMs }
45880
+ }
45881
+ );
45882
+ if (unresolvedMovement !== void 0) {
45883
+ await auditFailure(auditHome, args.account, void 0, "unfinished_movement", now);
45884
+ return skipped(args.account, "unfinished_movement");
45885
+ }
44413
45886
  const rawAddress = await (args.addressReader ?? ((account) => args.store.readAddress(account)))(
44414
45887
  args.account
44415
45888
  );
@@ -44436,12 +45909,12 @@ async function sweepAboveCeilingLocked(args) {
44436
45909
  }
44437
45910
  throw error61;
44438
45911
  }
44439
- const self = siblingResult.siblings.find((candidate) => candidate.self) ?? siblingResult.siblings.find((candidate) => sameAddress2(candidate.address, address2));
45912
+ const self = siblingResult.siblings.find((candidate) => candidate.self) ?? siblingResult.siblings.find((candidate) => sameAddress3(candidate.address, address2));
44440
45913
  if (self?.status === "revoked") return skipped(args.account, "revoked");
44441
45914
  if (siblingResult.owner === null) return skipped(args.account, "no_known_owner");
44442
45915
  const serverOwner = siblingResult.owner;
44443
45916
  const linkedOwner = isAddress(entry.link.owner, { strict: false }) ? getAddress(entry.link.owner) : void 0;
44444
- if (linkedOwner === void 0 || !sameAddress2(linkedOwner, serverOwner)) {
45917
+ if (linkedOwner === void 0 || !sameAddress3(linkedOwner, serverOwner)) {
44445
45918
  safeWarn(
44446
45919
  args.warn,
44447
45920
  `Refusing to sweep ${args.account}: its stored owner differs from the linked server owner.`
@@ -44469,12 +45942,30 @@ async function sweepAboveCeilingLocked(args) {
44469
45942
  CEILING_SWEEP_GUARD_MS
44470
45943
  );
44471
45944
  if (!claimed) return skipped(args.account, "throttled");
45945
+ if (pending !== void 0) {
45946
+ if (!sameAddress3(pending.owner, owner)) {
45947
+ return {
45948
+ account: args.account,
45949
+ status: "unknown",
45950
+ reason: "pending_owner_changed",
45951
+ amountUsd: formatAtomicUsd(BigInt(pending.amountAtomic))
45952
+ };
45953
+ }
45954
+ }
45955
+ const parent = await args.resolveParent?.(args.account);
45956
+ const target = sweepTarget({
45957
+ account: args.account,
45958
+ owner,
45959
+ ...parent === void 0 ? {} : { parent }
45960
+ });
44472
45961
  if (pending !== void 0) {
44473
45962
  const reconciled = await executePendingSweep({
44474
45963
  args,
44475
45964
  pending,
44476
45965
  address: address2,
44477
45966
  owner,
45967
+ target,
45968
+ sweepParent: parent?.address,
44478
45969
  readBalance: readBalance2
44479
45970
  });
44480
45971
  if (reconciled !== void 0) return reconciled;
@@ -44482,7 +45973,7 @@ async function sweepAboveCeilingLocked(args) {
44482
45973
  }
44483
45974
  await args.store.reload();
44484
45975
  const currentEntry = args.store.entry(args.account);
44485
- if (currentEntry?.link === void 0 || !sameOrigin(currentEntry.link.apiBase, args.apiBase) || !sameAddress2(currentEntry.link.owner, owner)) {
45976
+ if (currentEntry?.link === void 0 || !sameOrigin(currentEntry.link.apiBase, args.apiBase) || !sameAddress3(currentEntry.link.owner, owner)) {
44486
45977
  return skipped(args.account, "not_linked");
44487
45978
  }
44488
45979
  const currentCeiling = args.store.ceilingCaps(args.account).effectiveCeilingAtomic;
@@ -44498,8 +45989,9 @@ async function sweepAboveCeilingLocked(args) {
44498
45989
  args.signal?.throwIfAborted();
44499
45990
  pending = {
44500
45991
  owner,
45992
+ target,
44501
45993
  amountAtomic: amountAtomic.toString(),
44502
- nonce: (args.randomNonce ?? (() => bytesToHex(randomBytes4(32))))(),
45994
+ nonce: (args.randomNonce ?? (() => bytesToHex(randomBytes7(32))))(),
44503
45995
  createdAt: new Date(now()).toISOString(),
44504
45996
  status: "planned"
44505
45997
  };
@@ -44513,6 +46005,8 @@ async function sweepAboveCeilingLocked(args) {
44513
46005
  pending: stored,
44514
46006
  address: address2,
44515
46007
  owner,
46008
+ target,
46009
+ sweepParent: parent?.address,
44516
46010
  readBalance: readBalance2
44517
46011
  }) ?? skipped(args.account, "below_threshold");
44518
46012
  } catch (error61) {
@@ -44522,22 +46016,28 @@ async function sweepAboveCeilingLocked(args) {
44522
46016
  }
44523
46017
  }
44524
46018
  async function executePendingSweep(input2) {
44525
- const { args, pending, address: address2, owner, readBalance: readBalance2 } = input2;
46019
+ const {
46020
+ args,
46021
+ pending,
46022
+ address: address2,
46023
+ owner,
46024
+ target: currentTarget,
46025
+ sweepParent: currentSweepParent,
46026
+ readBalance: readBalance2
46027
+ } = input2;
44526
46028
  const now = args.now ?? Date.now;
44527
46029
  const auditHome = args.auditHome ?? args.store.home;
44528
- if (!sameAddress2(pending.owner, owner)) {
44529
- return {
44530
- account: args.account,
44531
- status: "unknown",
44532
- reason: "pending_owner_changed",
44533
- amountUsd: formatAtomicUsd(BigInt(pending.amountAtomic))
44534
- };
44535
- }
44536
46030
  const amountAtomic = BigInt(pending.amountAtomic);
44537
46031
  const amountUsd = formatAtomicUsd(amountAtomic);
44538
- const target = sweepTarget({ account: args.account, owner });
44539
46032
  const hasJournal = await hasTransferJournal(auditHome, args.account, pending.nonce);
44540
46033
  const resume = pending.status === "unknown" || hasJournal;
46034
+ const storedTarget = getAddress(pending.target ?? owner);
46035
+ if (!resume && !sameAddress3(storedTarget, currentTarget)) {
46036
+ await clearPendingSweep(args.store, args.account, pending.nonce);
46037
+ return void 0;
46038
+ }
46039
+ const target = resume ? storedTarget : currentTarget;
46040
+ const sweepParent = resume ? !sameAddress3(target, owner) ? target : void 0 : currentSweepParent;
44541
46041
  try {
44542
46042
  const result = await (args.transfer ?? transferBetweenAccounts)({
44543
46043
  store: args.store,
@@ -44548,6 +46048,7 @@ async function executePendingSweep(input2) {
44548
46048
  amountUsd,
44549
46049
  network: "base",
44550
46050
  purpose: "sweep",
46051
+ ...sweepParent === void 0 ? {} : { sweepParent },
44551
46052
  ...resume ? { resume: pending.nonce } : { nonce: pending.nonce },
44552
46053
  unlock: guardedSweepUnlock(args, address2, owner, amountAtomic, readBalance2),
44553
46054
  ...args.fetchImpl === void 0 ? {} : { fetchImpl: args.fetchImpl },
@@ -44685,7 +46186,7 @@ function guardedSweepUnlock(args, address2, owner, amountAtomic, readBalance2) {
44685
46186
  args.signal?.throwIfAborted();
44686
46187
  await args.store.reload();
44687
46188
  const entry = args.store.entry(account);
44688
- if (entry?.link === void 0 || !sameOrigin(entry.link.apiBase, args.apiBase) || !sameAddress2(entry.link.owner, owner)) {
46189
+ if (entry?.link === void 0 || !sameOrigin(entry.link.apiBase, args.apiBase) || !sameAddress3(entry.link.owner, owner)) {
44689
46190
  throw new CeilingPolicyChangedError();
44690
46191
  }
44691
46192
  const ceiling = args.store.ceilingCaps(account).effectiveCeilingAtomic;
@@ -44751,77 +46252,17 @@ var CeilingPolicyChangedError = class extends Error {
44751
46252
  code = "policy_changed";
44752
46253
  };
44753
46254
  async function withCeilingSweepLock(args, operation) {
44754
- const path = `${args.store.registryPath}.${args.account}.ceiling.lock`;
44755
- const waitMs = args.timeoutMs ?? CEILING_SWEEP_TIMEOUT_MS;
44756
- const deadline = Date.now() + Math.max(0, waitMs);
44757
- const staleAfterMs = Math.max(12e4, waitMs * 3);
44758
- const owner = JSON.stringify({ pid: process.pid, token: randomBytes4(16).toString("hex") });
44759
- let handle;
44760
- while (handle === void 0) {
44761
- args.signal?.throwIfAborted();
44762
- try {
44763
- handle = await open7(path, "wx", 384);
44764
- await handle.writeFile(owner, "utf8");
44765
- await handle.sync();
44766
- } catch (error61) {
44767
- if (!(error61 instanceof Error && "code" in error61 && error61.code === "EEXIST")) throw error61;
44768
- await removeStaleSweepLock(path, staleAfterMs);
44769
- if (Date.now() >= deadline) throw new CeilingSweepTimeoutError();
44770
- await new Promise((resolve) => setTimeout(resolve, 20));
44771
- }
44772
- }
44773
- const ownedHandle = handle;
44774
- const heartbeat = setInterval(
44775
- () => {
44776
- const time3 = /* @__PURE__ */ new Date();
44777
- void ownedHandle.utimes(time3, time3).catch(() => void 0);
44778
- },
44779
- Math.min(3e4, Math.max(1e3, Math.floor(staleAfterMs / 3)))
44780
- );
44781
- heartbeat.unref();
44782
46255
  try {
44783
- return await operation();
44784
- } finally {
44785
- clearInterval(heartbeat);
44786
- await ownedHandle.close();
44787
- await unlinkOwnedSweepLock(path, owner);
44788
- }
44789
- }
44790
- async function removeStaleSweepLock(path, staleAfterMs) {
44791
- try {
44792
- const metadata = await stat5(path);
44793
- if (Date.now() - metadata.mtimeMs <= staleAfterMs) return;
44794
- const owner = await readFile5(path, "utf8").catch(() => "");
44795
- const pid = lockOwnerPid(owner);
44796
- if (pid !== void 0 && processIsAlive(pid)) return;
44797
- await unlinkOwnedSweepLock(path, owner);
44798
- } catch (error61) {
44799
- if (!isMissingFile(error61)) throw error61;
44800
- }
44801
- }
44802
- async function unlinkOwnedSweepLock(path, owner) {
44803
- try {
44804
- if (await readFile5(path, "utf8") === owner) await unlink5(path);
44805
- } catch (error61) {
44806
- if (!isMissingFile(error61)) throw error61;
44807
- }
44808
- }
44809
- function lockOwnerPid(owner) {
44810
- try {
44811
- const value = JSON.parse(owner);
44812
- if (typeof value !== "object" || value === null) return void 0;
44813
- const pid = Reflect.get(value, "pid");
44814
- return typeof pid === "number" && Number.isSafeInteger(pid) && pid > 0 ? pid : void 0;
44815
- } catch {
44816
- return void 0;
44817
- }
44818
- }
44819
- function processIsAlive(pid) {
44820
- try {
44821
- process.kill(pid, 0);
44822
- return true;
46256
+ return await withAccountMovementLock(
46257
+ args.store.home,
46258
+ args.account,
46259
+ args.timeoutMs ?? CEILING_SWEEP_TIMEOUT_MS,
46260
+ operation,
46261
+ args.signal
46262
+ );
44823
46263
  } catch (error61) {
44824
- return error61 instanceof Error && "code" in error61 && error61.code === "EPERM";
46264
+ if (error61 instanceof AccountMovementLockedError) throw new CeilingSweepTimeoutError();
46265
+ throw error61;
44825
46266
  }
44826
46267
  }
44827
46268
  function defaultBalanceReader(args) {
@@ -44868,7 +46309,7 @@ function sameOrigin(left, right) {
44868
46309
  return false;
44869
46310
  }
44870
46311
  }
44871
- function sameAddress2(left, right) {
46312
+ function sameAddress3(left, right) {
44872
46313
  return isAddress(left, { strict: false }) && isAddress(right, { strict: false }) && getAddress(left) === getAddress(right);
44873
46314
  }
44874
46315
  function safeWarn(warn, message) {
@@ -44903,11 +46344,160 @@ async function safeAppendAudit(home, record2, now) {
44903
46344
  await appendAudit(home, record2, { now: () => new Date(now()) }).catch(() => void 0);
44904
46345
  }
44905
46346
 
46347
+ // src/swarm.ts
46348
+ import { readdir as readdir4, readFile as readFile10, stat as stat8, unlink as unlink8 } from "node:fs/promises";
46349
+ import { join as join6 } from "node:path";
46350
+ var SWARM_NAME_PATTERN = /^[a-z][a-z0-9-]{0,15}$/;
46351
+ var SWARM_ROLE_PATTERN = /^[a-z][a-z0-9]{0,7}$/;
46352
+ var atomicSchema = external_exports.string().regex(/^\d+$/);
46353
+ var addressSchema2 = external_exports.string().regex(/^0x[0-9a-fA-F]{40}$/);
46354
+ var capsSchema = external_exports.strictObject({ perCallAtomic: atomicSchema, perDayAtomic: atomicSchema });
46355
+ var movementIdSchema2 = external_exports.string().regex(/^mv_[A-Za-z0-9_-]{8,128}$/u);
46356
+ var amountUsdSchema = external_exports.string().regex(/^\d+\.\d{2}$/u);
46357
+ var setupFundingStatusSchema = external_exports.enum(["planned", "sent", "incomplete"]);
46358
+ var blockedFundingLegSchema = external_exports.strictObject({
46359
+ to: external_exports.string().trim().min(1),
46360
+ amountUsd: amountUsdSchema,
46361
+ reason: external_exports.string().trim().min(1)
46362
+ });
46363
+ var setupFundingSchema = external_exports.strictObject({
46364
+ amountUsd: amountUsdSchema,
46365
+ from: walletNameSchema,
46366
+ movementId: movementIdSchema2.optional(),
46367
+ status: setupFundingStatusSchema,
46368
+ policy: external_exports.strictObject({
46369
+ strategy: external_exports.enum(["targets", "even", "weights"]),
46370
+ movementId: movementIdSchema2.optional(),
46371
+ status: setupFundingStatusSchema,
46372
+ blocked: external_exports.array(blockedFundingLegSchema).optional()
46373
+ }).optional()
46374
+ });
46375
+ var accountSteps = {
46376
+ creating: external_exports.boolean().default(false),
46377
+ created: external_exports.boolean(),
46378
+ capped: external_exports.boolean(),
46379
+ linked: external_exports.boolean()
46380
+ };
46381
+ var treasurySchema = external_exports.strictObject({
46382
+ account: walletNameSchema,
46383
+ // A planned account has no address until its vault derivation succeeds.
46384
+ address: addressSchema2.optional(),
46385
+ steps: external_exports.strictObject(accountSteps)
46386
+ }).refine(
46387
+ (value) => !value.steps.created || value.address !== void 0,
46388
+ "Created accounts require an address."
46389
+ );
46390
+ var memberSchema = external_exports.strictObject({
46391
+ account: walletNameSchema,
46392
+ address: addressSchema2.optional(),
46393
+ role: external_exports.string().regex(SWARM_ROLE_PATTERN),
46394
+ weight: external_exports.number().int().positive(),
46395
+ targetAtomic: atomicSchema,
46396
+ ceilingAtomic: atomicSchema.nullable(),
46397
+ steps: external_exports.strictObject({ ...accountSteps, profiled: external_exports.boolean() })
46398
+ }).refine(
46399
+ (value) => !value.steps.created || value.address !== void 0,
46400
+ "Created accounts require an address."
46401
+ );
46402
+ var swarmFileSchema = external_exports.strictObject({
46403
+ v: external_exports.literal(1),
46404
+ name: external_exports.string().regex(SWARM_NAME_PATTERN),
46405
+ device: external_exports.string().regex(DEVICE_NAME_PATTERN),
46406
+ network: external_exports.enum([BASE_MAINNET_CAIP2, ARC_MAINNET_CAIP2]),
46407
+ createdAt: external_exports.iso.datetime(),
46408
+ treasury: treasurySchema,
46409
+ members: external_exports.array(memberSchema),
46410
+ setupFunding: setupFundingSchema.optional(),
46411
+ policy: external_exports.strictObject({
46412
+ strategy: external_exports.enum(["targets", "even", "weights"]),
46413
+ treasuryCaps: capsSchema
46414
+ })
46415
+ }).refine(
46416
+ (value) => (/* @__PURE__ */ new Set([value.treasury.account, ...value.members.map((member) => member.account)])).size === value.members.length + 1,
46417
+ "Swarm accounts must be unique."
46418
+ );
46419
+ var SwarmError = class extends Error {
46420
+ constructor(code, message) {
46421
+ super(message);
46422
+ this.code = code;
46423
+ this.name = "SwarmError";
46424
+ }
46425
+ code;
46426
+ };
46427
+ function swarmsDirectory(home) {
46428
+ return join6(home, "swarms");
46429
+ }
46430
+ function swarmFilePath(home, name) {
46431
+ assertName(name);
46432
+ return join6(swarmsDirectory(home), `${name}.json`);
46433
+ }
46434
+ async function readSwarm(home, name) {
46435
+ const path = swarmFilePath(home, name);
46436
+ try {
46437
+ const swarm = parseSwarm(JSON.parse(await readFile10(path, "utf8")));
46438
+ if (swarm.name !== name)
46439
+ throw new SwarmError("invalid_swarm", "Swarm name does not match its file.");
46440
+ return swarm;
46441
+ } catch (error61) {
46442
+ if (isMissingFile(error61)) throw new SwarmError("swarm_not_found", `No swarm named ${name}.`);
46443
+ if (error61 instanceof SwarmError) throw error61;
46444
+ throw new SwarmError("invalid_swarm", `Invalid swarm file for ${name}.`);
46445
+ }
46446
+ }
46447
+ async function listSwarms(home) {
46448
+ let files;
46449
+ try {
46450
+ files = await readdir4(swarmsDirectory(home));
46451
+ } catch (error61) {
46452
+ if (isMissingFile(error61)) return [];
46453
+ throw error61;
46454
+ }
46455
+ return await Promise.all(
46456
+ files.filter((file2) => file2.endsWith(".json")).sort().map((file2) => readSwarm(home, file2.slice(0, -5)))
46457
+ );
46458
+ }
46459
+ function swarmParentResolver(home) {
46460
+ return async (account) => {
46461
+ const swarms = await listSwarms(home);
46462
+ const matches = swarms.filter(
46463
+ (swarm) => swarm.members.some((member) => member.account === account)
46464
+ );
46465
+ if (matches.length > 1)
46466
+ throw new SwarmError(
46467
+ "account_in_other_swarm",
46468
+ `Account ${account} belongs to multiple swarms.`
46469
+ );
46470
+ const treasury = matches[0]?.treasury;
46471
+ if (treasury === void 0) return void 0;
46472
+ if (treasury.address === void 0)
46473
+ throw new SwarmError("invalid_swarm", "The swarm treasury is not created yet.");
46474
+ return { account: treasury.account, address: treasury.address };
46475
+ };
46476
+ }
46477
+ function parseSwarm(value) {
46478
+ const result = swarmFileSchema.safeParse(value);
46479
+ if (!result.success) throw new SwarmError("invalid_swarm", "Invalid swarm state.");
46480
+ return result.data;
46481
+ }
46482
+ function assertName(name) {
46483
+ if (!SWARM_NAME_PATTERN.test(name))
46484
+ throw new SwarmError(
46485
+ "invalid_swarm",
46486
+ "A swarm name must be 1 to 16 lowercase letters, digits or dashes, starting with a letter."
46487
+ );
46488
+ }
46489
+
44906
46490
  // src/x402-pay.ts
44907
- import { randomUUID as randomUUID2 } from "node:crypto";
46491
+ import { randomBytes as randomBytes8, randomUUID as randomUUID2 } from "node:crypto";
46492
+
46493
+ // src/run-budget.ts
46494
+ var RunBudgetError = class extends Error {
46495
+ name = "RunBudgetError";
46496
+ code = "run_budget_exceeded";
46497
+ };
44908
46498
 
44909
46499
  // src/support-report.ts
44910
- var VAPI_CLIENT_VERSION = "0.7.0";
46500
+ var VAPI_CLIENT_VERSION = "0.9.0";
44911
46501
 
44912
46502
  // src/x402-pay.ts
44913
46503
  var X402PaymentError = class extends Error {
@@ -44926,6 +46516,7 @@ var X402PaymentError = class extends Error {
44926
46516
  };
44927
46517
  async function payRequest(args) {
44928
46518
  const startedAt = args.now ?? /* @__PURE__ */ new Date();
46519
+ assertAllowanceCurrent(startedAt, args.allowanceExpiresAt);
44929
46520
  const nowMs = args.nowMs ?? (() => performance.now());
44930
46521
  const startedMs = nowMs();
44931
46522
  const receiptId = randomUUID2();
@@ -44965,8 +46556,10 @@ async function payRequest(args) {
44965
46556
  ...args.preferNetworks === void 0 ? {} : { preferNetworks: args.preferNetworks },
44966
46557
  ...args.paymentId === void 0 ? {} : { paymentId: args.paymentId },
44967
46558
  now: startedAt,
46559
+ ...args.allowanceExpiresAt === void 0 ? {} : { allowanceExpiresAt: args.allowanceExpiresAt },
44968
46560
  nowMs,
44969
46561
  trace,
46562
+ ...args.runBudget === void 0 ? {} : { runBudget: args.runBudget },
44970
46563
  ...args.ceilingSweep === void 0 ? {} : { ceilingSweep: args.ceilingSweep }
44971
46564
  });
44972
46565
  response = completed.response;
@@ -45000,6 +46593,8 @@ async function payRequest(args) {
45000
46593
  return { response, receipt };
45001
46594
  }
45002
46595
  async function completeX402Payment(args) {
46596
+ const paymentTime = args.now ?? /* @__PURE__ */ new Date();
46597
+ assertAllowanceCurrent(paymentTime, args.allowanceExpiresAt);
45003
46598
  const nowMs = args.nowMs ?? (() => performance.now());
45004
46599
  const trace = args.trace;
45005
46600
  const quoteStarted = args.quoteStartedMs ?? nowMs();
@@ -45022,11 +46617,29 @@ async function completeX402Payment(args) {
45022
46617
  if (trace) trace.capsApplied = true;
45023
46618
  assertMaximumAtomic(quote.amountAtomic, args.maxAtomic);
45024
46619
  assertMaxPrice(quote.amountAtomic, args.maxPriceUsd);
45025
- await reserveSpend(quote.amountAtomic, args.caps, {
46620
+ const spendReservation = {
45026
46621
  ledgerPath: args.ledgerPath ?? getVapiPaths().ledger,
45027
46622
  now: args.now,
45028
46623
  ...args.wallet === void 0 ? {} : { wallet: args.wallet }
45029
- });
46624
+ };
46625
+ let reservationId;
46626
+ let reservedOn;
46627
+ if (args.runBudget === void 0) {
46628
+ await reserveSpend(quote.amountAtomic, args.caps, spendReservation);
46629
+ } else {
46630
+ reservationId = args.paymentId ?? `x402_${randomBytes8(12).toString("hex")}`;
46631
+ const reservation = await reserveSpend(quote.amountAtomic, args.caps, {
46632
+ ...spendReservation,
46633
+ reservationId
46634
+ });
46635
+ reservedOn = reservation.date;
46636
+ try {
46637
+ args.runBudget.reserve(quote.amountAtomic, reservationId);
46638
+ } catch (error61) {
46639
+ await releaseUnsentReservation(args, quote.amountAtomic, reservationId, reservedOn);
46640
+ throw error61;
46641
+ }
46642
+ }
45030
46643
  const signStarted = nowMs();
45031
46644
  let payment;
45032
46645
  try {
@@ -45037,6 +46650,7 @@ async function completeX402Payment(args) {
45037
46650
  allowPrivateNetwork: args.config.allowPrivateNetwork ?? false
45038
46651
  }),
45039
46652
  ...args.now === void 0 ? {} : { nowSeconds: Math.floor(args.now.getTime() / 1e3) },
46653
+ ...args.allowanceExpiresAt === void 0 ? {} : { maximumValidBeforeSeconds: Math.floor(args.allowanceExpiresAt.getTime() / 1e3) },
45040
46654
  ...args.paymentId === void 0 ? {} : { paymentId: args.paymentId }
45041
46655
  });
45042
46656
  const payer2 = isSvmPaymentRequirements(quote.accepted) ? args.account.solana?.address ?? args.account.address : args.account.address;
@@ -45048,6 +46662,17 @@ async function completeX402Payment(args) {
45048
46662
  trace.authorization = { from: from15, nonce, validBefore };
45049
46663
  }
45050
46664
  }
46665
+ } catch (error61) {
46666
+ if (args.runBudget !== void 0 && reservationId !== void 0 && reservedOn !== void 0) {
46667
+ const released = await releaseUnsentReservation(
46668
+ args,
46669
+ quote.amountAtomic,
46670
+ reservationId,
46671
+ reservedOn
46672
+ );
46673
+ if (released) args.runBudget.release(reservationId);
46674
+ }
46675
+ throw error61;
45051
46676
  } finally {
45052
46677
  if (trace) trace.phases.signMs = elapsed(nowMs, signStarted);
45053
46678
  }
@@ -45182,6 +46807,26 @@ async function completeX402Payment(args) {
45182
46807
  paidAttempt.finish();
45183
46808
  }
45184
46809
  }
46810
+ function assertAllowanceCurrent(now, expiresAt) {
46811
+ if (expiresAt === void 0 || now.getTime() < expiresAt.getTime()) return;
46812
+ throw new Error(
46813
+ `Remote allowance expired at ${expiresAt.toISOString()}. Start a new Railway run before paying.`
46814
+ );
46815
+ }
46816
+ async function releaseUnsentReservation(args, amountAtomic, reservationId, reservedOn) {
46817
+ try {
46818
+ await releaseSpend(amountAtomic, {
46819
+ reservedOn,
46820
+ ledgerPath: args.ledgerPath ?? getVapiPaths().ledger,
46821
+ now: args.now,
46822
+ ...args.wallet === void 0 ? {} : { wallet: args.wallet },
46823
+ reservationId
46824
+ });
46825
+ return true;
46826
+ } catch {
46827
+ return false;
46828
+ }
46829
+ }
45185
46830
  async function selectQuote(args) {
45186
46831
  const networks = toX402Networks(args.config.networks);
45187
46832
  const waitFor = args.challenge.waitFor.bind(args.challenge);
@@ -45276,6 +46921,9 @@ function sanitizeThrownError(error61, secrets) {
45276
46921
  if (error61 instanceof SpendCapError) {
45277
46922
  return new SpendCapError(error61.code, message);
45278
46923
  }
46924
+ if (error61 instanceof RunBudgetError) {
46925
+ return new RunBudgetError(message);
46926
+ }
45279
46927
  if (error61 instanceof X402PaymentError) {
45280
46928
  return new X402PaymentError(error61.code, message, error61.possibleSettlement, {
45281
46929
  ...cause === void 0 ? {} : { cause },
@@ -45365,6 +47013,7 @@ function directReceipt(args, request, trace, receiptId, startedAt, latencyMs, re
45365
47013
  resourceUrl: request.url,
45366
47014
  method: request.method,
45367
47015
  source: args.source ?? "direct",
47016
+ ...args.run === void 0 ? {} : { run: args.run },
45368
47017
  ...trace.quote ? { quote: trace.quote } : {},
45369
47018
  ...trace.payer && result.outcome !== "declined_policy" ? { payer: trace.payer } : {},
45370
47019
  ...trace.paymentId ? { paymentId: trace.paymentId } : {},
@@ -45392,7 +47041,7 @@ function receiptQuote(quote) {
45392
47041
  };
45393
47042
  }
45394
47043
  function paymentErrorOutcome(error61) {
45395
- if (error61 instanceof SpendCapError || error61 instanceof X402Error && error61.code === "max_price_exceeded") {
47044
+ if (error61 instanceof SpendCapError || error61 instanceof RunBudgetError || error61 instanceof X402Error && error61.code === "max_price_exceeded") {
45396
47045
  return "declined_policy";
45397
47046
  }
45398
47047
  if (error61 instanceof X402PaymentError) {
@@ -45409,7 +47058,7 @@ function errorCode(error61) {
45409
47058
  return "request_failed";
45410
47059
  }
45411
47060
  function receiptSafeErrorMessage(error61) {
45412
- if (error61 instanceof SpendCapError || error61 instanceof X402Error || error61 instanceof X402PaymentError) {
47061
+ if (error61 instanceof SpendCapError || error61 instanceof RunBudgetError || error61 instanceof X402Error || error61 instanceof X402PaymentError) {
45413
47062
  return error61.message;
45414
47063
  }
45415
47064
  return "The x402 request failed before it completed.";
@@ -45545,9 +47194,9 @@ async function buyRouterBalance(deps, tierUsd) {
45545
47194
  if (!isRouterTopupTier(tierUsd)) {
45546
47195
  throw new RouterClientError("http", "Router balance must use a $1, $5, $20, or $50 tier.");
45547
47196
  }
45548
- const { link, accessToken } = await purchaseCredentialSnapshot(deps);
47197
+ const { link: link2, accessToken } = await purchaseCredentialSnapshot(deps);
45549
47198
  const { response, receipt } = await payRequest({
45550
- url: `${trimTrailingSlashes(link.apiBase)}/api/router/top-up/${tierUsd}`,
47199
+ url: `${trimTrailingSlashes(link2.apiBase)}/api/router/top-up/${tierUsd}`,
45551
47200
  init: {
45552
47201
  method: "POST",
45553
47202
  headers: { authorization: `Bearer ${accessToken}`, accept: "application/json" }
@@ -45561,6 +47210,9 @@ async function buyRouterBalance(deps, tierUsd) {
45561
47210
  maxAtomic: BigInt(tierUsd) * 1000000n,
45562
47211
  preferNetworks: ["eip155:8453"],
45563
47212
  ...deps.now === void 0 ? {} : { now: deps.now },
47213
+ ...deps.allowanceExpiresAt === void 0 ? {} : { allowanceExpiresAt: deps.allowanceExpiresAt },
47214
+ ...deps.runBudget === void 0 ? {} : { runBudget: deps.runBudget },
47215
+ ...deps.run === void 0 ? {} : { run: deps.run },
45564
47216
  source: "router.topup",
45565
47217
  ceilingSweep: {
45566
47218
  account: deps.wallet,
@@ -45568,10 +47220,11 @@ async function buyRouterBalance(deps, tierUsd) {
45568
47220
  run: async (signal) => await sweepAboveCeiling({
45569
47221
  store: deps.wallets,
45570
47222
  secrets: deps.secrets,
45571
- apiBase: link.apiBase,
47223
+ apiBase: link2.apiBase,
45572
47224
  account: deps.wallet,
45573
47225
  config: deps.config,
45574
47226
  signal,
47227
+ resolveParent: swarmParentResolver(deps.wallets.home),
45575
47228
  ...deps.fetchImpl === void 0 ? {} : { fetchImpl: deps.fetchImpl },
45576
47229
  unlock: async () => deps.account
45577
47230
  })
@@ -45590,14 +47243,14 @@ async function buyRouterBalance(deps, tierUsd) {
45590
47243
  {
45591
47244
  event: "router.buy",
45592
47245
  wallet: deps.wallet,
45593
- owner: link.owner,
47246
+ owner: link2.owner,
45594
47247
  tty: Boolean(process.stderr.isTTY),
45595
47248
  detail: `$${tierUsd} on ${receipt.quote?.network ?? "eip155:8453"}`
45596
47249
  },
45597
47250
  deps.now === void 0 ? {} : { now: () => deps.now }
45598
47251
  );
45599
47252
  try {
45600
- await ensureBalanceRouterKey(deps, link);
47253
+ await ensureBalanceRouterKey(deps, link2);
45601
47254
  } catch {
45602
47255
  throw new RouterClientError(
45603
47256
  "http",
@@ -45636,7 +47289,12 @@ async function routerChat(deps, request) {
45636
47289
  config: deps.refill.config,
45637
47290
  caps,
45638
47291
  ...deps.refill.paths === void 0 ? {} : { paths: deps.refill.paths },
45639
- ...deps.refill.now === void 0 ? {} : { now: deps.refill.now }
47292
+ ...deps.refill.now === void 0 ? {} : {
47293
+ now: typeof deps.refill.now === "function" ? deps.refill.now() : deps.refill.now
47294
+ },
47295
+ ...deps.refill.allowanceExpiresAt === void 0 ? {} : { allowanceExpiresAt: deps.refill.allowanceExpiresAt },
47296
+ ...deps.refill.runBudget === void 0 ? {} : { runBudget: deps.refill.runBudget },
47297
+ ...deps.refill.run === void 0 ? {} : { run: deps.refill.run }
45640
47298
  },
45641
47299
  refill.tierUsd
45642
47300
  )).balance;
@@ -45648,6 +47306,7 @@ async function routerChat(deps, request) {
45648
47306
  tty: Boolean(process.stderr.isTTY),
45649
47307
  detail: refillDeclinedDetail(refillError)
45650
47308
  });
47309
+ if (isRunBudgetError(refillError)) throw refillError;
45651
47310
  }
45652
47311
  }
45653
47312
  const mayHaveBalance = usage === void 0 || (balance?.remainingUsd ?? 0) > 0;
@@ -45755,8 +47414,8 @@ async function ensureBalanceRouterKey(deps, paidLink) {
45755
47414
  deps.wallets,
45756
47415
  `${paidLink.clientId}:router-balance-key`,
45757
47416
  async () => {
45758
- const link = await linkedWallet(deps);
45759
- if (!sameAgentLink(link, paidLink)) {
47417
+ const link2 = await linkedWallet(deps);
47418
+ if (!sameAgentLink(link2, paidLink)) {
45760
47419
  throw new RouterClientError(
45761
47420
  "http",
45762
47421
  "The agent link changed while fetching the Router balance key."
@@ -45775,7 +47434,7 @@ async function ensureBalanceRouterKey(deps, paidLink) {
45775
47434
  "/api/agents/self/router-balance-key",
45776
47435
  { method: "POST" },
45777
47436
  "The Router balance key request failed after payment.",
45778
- link
47437
+ link2
45779
47438
  );
45780
47439
  if (!response.ok) {
45781
47440
  throw new RouterClientError(
@@ -45796,7 +47455,7 @@ async function ensureBalanceRouterKey(deps, paidLink) {
45796
47455
  );
45797
47456
  }
45798
47457
  const returnedBase = validRouterBaseUrl(parsed.data.router_base_url);
45799
- const expectedBase = validRouterBaseUrl(link.routerBaseUrl ?? DEFAULT_ROUTER_BASE_URL);
47458
+ const expectedBase = validRouterBaseUrl(link2.routerBaseUrl ?? DEFAULT_ROUTER_BASE_URL);
45800
47459
  if (returnedBase !== expectedBase) {
45801
47460
  throw new RouterClientError(
45802
47461
  "http",
@@ -45815,8 +47474,8 @@ async function routerBalanceCredentialSnapshot(deps) {
45815
47474
  const observedLink = await linkedWallet(deps);
45816
47475
  try {
45817
47476
  return await withAgentCredentialLock(deps.wallets, observedLink.clientId, async () => {
45818
- const link = await linkedWallet(deps);
45819
- if (link.clientId !== observedLink.clientId) {
47477
+ const link2 = await linkedWallet(deps);
47478
+ if (link2.clientId !== observedLink.clientId) {
45820
47479
  throw new RouterClientError(
45821
47480
  "http",
45822
47481
  "The agent link changed while reading Router balance credentials."
@@ -45831,8 +47490,8 @@ async function routerBalanceCredentialSnapshot(deps) {
45831
47490
  }
45832
47491
  if (routerKey === void 0 || routerKey.length === 0) return void 0;
45833
47492
  return {
45834
- clientId: link.clientId,
45835
- routerBaseUrl: validRouterBaseUrl(link.routerBaseUrl ?? DEFAULT_ROUTER_BASE_URL),
47493
+ clientId: link2.clientId,
47494
+ routerBaseUrl: validRouterBaseUrl(link2.routerBaseUrl ?? DEFAULT_ROUTER_BASE_URL),
45836
47495
  routerKey
45837
47496
  };
45838
47497
  });
@@ -45847,8 +47506,8 @@ async function routerBalanceCredentialSnapshot(deps) {
45847
47506
  async function removeRejectedBalanceKey(deps, rejected) {
45848
47507
  if (!deps.secrets.available) return;
45849
47508
  await withAgentCredentialLock(deps.wallets, rejected.clientId, async () => {
45850
- const link = await linkedWallet(deps);
45851
- if (link.clientId !== rejected.clientId) return;
47509
+ const link2 = await linkedWallet(deps);
47510
+ if (link2.clientId !== rejected.clientId) return;
45852
47511
  const account = agentSecretAccounts(deps.wallet).routerBalance;
45853
47512
  if (await deps.secrets.get(account) === rejected.routerKey) {
45854
47513
  await deps.secrets.remove(account);
@@ -45897,16 +47556,20 @@ function budgetExhaustedError(resetsAt, status) {
45897
47556
  }
45898
47557
  function refillDeclinedDetail(error61) {
45899
47558
  if (error61 instanceof SpendCapError) return `${error61.name}: ${error61.code}`;
47559
+ if (isRunBudgetError(error61)) return `RunBudgetError: ${error61.code}`;
45900
47560
  if (error61 instanceof RouterClientError) {
45901
47561
  return `${error61.name}: ${error61.code}${error61.status === void 0 ? "" : ` (${error61.status})`}`;
45902
47562
  }
45903
47563
  return "Error: Router balance refill failed.";
45904
47564
  }
47565
+ function isRunBudgetError(error61) {
47566
+ return typeof error61 === "object" && error61 !== null && Reflect.get(error61, "code") === "run_budget_exceeded";
47567
+ }
45905
47568
  function isRouterTopupTier(value) {
45906
47569
  return ROUTER_TOPUP_TIERS.some((tier) => tier === value);
45907
47570
  }
45908
47571
  async function rotateRouterKey(deps) {
45909
- const link = await linkedWallet(deps);
47572
+ const link2 = await linkedWallet(deps);
45910
47573
  if (!deps.secrets.available) {
45911
47574
  throw new Error(
45912
47575
  "No OS secret store is available on this machine, so the Router key cannot be stored safely."
@@ -45917,7 +47580,7 @@ async function rotateRouterKey(deps) {
45917
47580
  "/api/agents/self/router-key",
45918
47581
  { method: "POST" },
45919
47582
  "The vAPI Router key rotation request failed.",
45920
- link
47583
+ link2
45921
47584
  );
45922
47585
  if (!response.ok) {
45923
47586
  throw new RouterClientError(
@@ -45932,9 +47595,9 @@ async function rotateRouterKey(deps) {
45932
47595
  }
45933
47596
  validRouterBaseUrl(parsed.data.router_base_url);
45934
47597
  try {
45935
- await withAgentCredentialLock(deps.wallets, link.clientId, async () => {
47598
+ await withAgentCredentialLock(deps.wallets, link2.clientId, async () => {
45936
47599
  const currentLink = await linkedWallet(deps);
45937
- if (!sameAgentLink(currentLink, link)) {
47600
+ if (!sameAgentLink(currentLink, link2)) {
45938
47601
  throw new RouterClientError("http", "The agent link changed during Router key rotation.");
45939
47602
  }
45940
47603
  const account = agentSecretAccounts(deps.wallet).routerStake;
@@ -45998,15 +47661,15 @@ async function routerCredentialSnapshot(deps) {
45998
47661
  const observedLink = await linkedWallet(deps);
45999
47662
  try {
46000
47663
  return await withAgentCredentialLock(deps.wallets, observedLink.clientId, async () => {
46001
- const link = await linkedWallet(deps);
46002
- if (link.clientId !== observedLink.clientId) {
47664
+ const link2 = await linkedWallet(deps);
47665
+ if (link2.clientId !== observedLink.clientId) {
46003
47666
  throw new RouterClientError(
46004
47667
  "http",
46005
47668
  "The agent link changed while reading Router credentials."
46006
47669
  );
46007
47670
  }
46008
47671
  return {
46009
- routerBaseUrl: validRouterBaseUrl(link.routerBaseUrl ?? DEFAULT_ROUTER_BASE_URL),
47672
+ routerBaseUrl: validRouterBaseUrl(link2.routerBaseUrl ?? DEFAULT_ROUTER_BASE_URL),
46010
47673
  routerKey: await storedRouterKey(deps)
46011
47674
  };
46012
47675
  });
@@ -46027,9 +47690,9 @@ async function linkedWallet(deps) {
46027
47690
  } catch {
46028
47691
  throw new RouterClientError("http", "The wallet link could not be read.");
46029
47692
  }
46030
- const link = deps.wallets.entry(deps.wallet)?.link;
46031
- if (link === void 0) throw notLinkedError2();
46032
- return link;
47693
+ const link2 = deps.wallets.entry(deps.wallet)?.link;
47694
+ if (link2 === void 0) throw notLinkedError2();
47695
+ return link2;
46033
47696
  }
46034
47697
  async function storedRouterKey(deps) {
46035
47698
  if (!deps.secrets.available) throw noRouterKeyError();
@@ -46046,7 +47709,7 @@ function sameAgentLink(left, right) {
46046
47709
  return left.apiBase === right.apiBase && left.clientId === right.clientId && left.owner === right.owner && left.label === right.label && left.linkedAt === right.linkedAt && left.routerBaseUrl === right.routerBaseUrl && left.scopes.length === right.scopes.length && left.scopes.every((scope, index2) => scope === right.scopes[index2]);
46047
47710
  }
46048
47711
  async function consoleRequest(deps, path, init, failureMessage, knownLink) {
46049
- const link = knownLink ?? await linkedWallet(deps);
47712
+ const link2 = knownLink ?? await linkedWallet(deps);
46050
47713
  try {
46051
47714
  return await agentFetch(
46052
47715
  {
@@ -46055,7 +47718,7 @@ async function consoleRequest(deps, path, init, failureMessage, knownLink) {
46055
47718
  wallet: deps.wallet,
46056
47719
  ...deps.fetchImpl === void 0 ? {} : { fetchImpl: deps.fetchImpl }
46057
47720
  },
46058
- `${trimTrailingSlashes(link.apiBase)}${path}`,
47721
+ `${trimTrailingSlashes(link2.apiBase)}${path}`,
46059
47722
  init
46060
47723
  );
46061
47724
  } catch (error61) {
@@ -46066,7 +47729,7 @@ async function consoleRequest(deps, path, init, failureMessage, knownLink) {
46066
47729
  }
46067
47730
  }
46068
47731
  async function verifyAgentLink(deps) {
46069
- const link = await linkedWallet(deps);
47732
+ const link2 = await linkedWallet(deps);
46070
47733
  try {
46071
47734
  await agentFetch(
46072
47735
  {
@@ -46075,7 +47738,7 @@ async function verifyAgentLink(deps) {
46075
47738
  wallet: deps.wallet,
46076
47739
  ...deps.fetchImpl === void 0 ? {} : { fetchImpl: deps.fetchImpl }
46077
47740
  },
46078
- `${trimTrailingSlashes(link.apiBase)}/api/agents/self/router`,
47741
+ `${trimTrailingSlashes(link2.apiBase)}/api/agents/self/router`,
46079
47742
  { method: "GET" }
46080
47743
  );
46081
47744
  } catch (error61) {