@vapi-network/core 0.7.0 → 0.8.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/account-add.d.ts +8 -1
- package/dist/account-add.d.ts.map +1 -1
- package/dist/account-movement-lock.d.ts +10 -0
- package/dist/account-movement-lock.d.ts.map +1 -0
- package/dist/agent-link.d.ts +1 -0
- package/dist/agent-link.d.ts.map +1 -1
- package/dist/agent-link.js +21 -10
- package/dist/agent-link.js.map +1 -1
- package/dist/agent-profile.d.ts +14 -2
- package/dist/agent-profile.d.ts.map +1 -1
- package/dist/agent-run.d.ts +12 -0
- package/dist/agent-run.d.ts.map +1 -0
- package/dist/allocation.d.ts +35 -4
- package/dist/allocation.d.ts.map +1 -1
- package/dist/api-key.js.map +1 -1
- package/dist/atomic-file.d.ts +23 -0
- package/dist/atomic-file.d.ts.map +1 -0
- package/dist/audit.d.ts +4 -1
- package/dist/audit.d.ts.map +1 -1
- package/dist/backup.d.ts +153 -32
- package/dist/backup.d.ts.map +1 -1
- package/dist/backup.js +2535 -139
- package/dist/backup.js.map +3 -3
- package/dist/call-contracts-wire.d.ts +12 -12
- package/dist/ceiling.d.ts +8 -0
- package/dist/ceiling.d.ts.map +1 -1
- package/dist/cloud-backup.d.ts +8 -10
- package/dist/cloud-backup.d.ts.map +1 -1
- package/dist/cloud-backup.js +2806 -381
- package/dist/cloud-backup.js.map +3 -3
- package/dist/config.d.ts.map +1 -1
- package/dist/distribute.d.ts +9 -1
- package/dist/distribute.d.ts.map +1 -1
- package/dist/index.d.ts +8 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +5206 -1270
- package/dist/index.js.map +3 -3
- package/dist/marketplace-contracts.d.ts +14 -14
- package/dist/member-bundle.d.ts +110 -0
- package/dist/member-bundle.d.ts.map +1 -0
- package/dist/movement-open.d.ts +10 -0
- package/dist/movement-open.d.ts.map +1 -0
- package/dist/movement.d.ts +77 -7
- package/dist/movement.d.ts.map +1 -1
- package/dist/receipts.d.ts +9 -0
- package/dist/receipts.d.ts.map +1 -1
- package/dist/router-client.d.ts +9 -1
- package/dist/router-client.d.ts.map +1 -1
- package/dist/router-client.js +2073 -607
- package/dist/router-client.js.map +3 -3
- package/dist/run-budget.d.ts +17 -0
- package/dist/run-budget.d.ts.map +1 -0
- package/dist/secret-store.d.ts +6 -0
- package/dist/secret-store.d.ts.map +1 -1
- package/dist/secrets.d.ts +2 -1
- package/dist/secrets.d.ts.map +1 -1
- package/dist/secrets.js +884 -48
- package/dist/secrets.js.map +3 -3
- package/dist/support-report.d.ts +1 -1
- package/dist/swarm-capital.d.ts +147 -0
- package/dist/swarm-capital.d.ts.map +1 -0
- package/dist/swarm.d.ts +224 -0
- package/dist/swarm.d.ts.map +1 -0
- package/dist/transfer-journal.d.ts +11 -0
- package/dist/transfer-journal.d.ts.map +1 -0
- package/dist/transfer.d.ts +4 -1
- package/dist/transfer.d.ts.map +1 -1
- package/dist/vault.d.ts +11 -0
- package/dist/vault.d.ts.map +1 -1
- package/dist/wallet-store.d.ts +19 -8
- package/dist/wallet-store.d.ts.map +1 -1
- package/dist/x402-pay.d.ts +7 -0
- package/dist/x402-pay.d.ts.map +1 -1
- package/dist/x402.d.ts +3 -0
- package/dist/x402.d.ts.map +1 -1
- package/package.json +1 -1
package/dist/router-client.js
CHANGED
|
@@ -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:
|
|
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 ?
|
|
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
|
|
28526
|
-
import { dirname as
|
|
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
|
|
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"
|
|
@@ -37110,31 +37337,269 @@ var configSchema = external_exports.object({
|
|
|
37110
37337
|
}
|
|
37111
37338
|
}
|
|
37112
37339
|
});
|
|
37113
|
-
function getVapiPaths(directory = process.env.VAPI_HOME?.trim() ||
|
|
37340
|
+
function getVapiPaths(directory = process.env.VAPI_HOME?.trim() || join2(homedir(), ".vapi")) {
|
|
37114
37341
|
return {
|
|
37115
37342
|
directory,
|
|
37116
|
-
agentsDir:
|
|
37117
|
-
config:
|
|
37118
|
-
keystore:
|
|
37119
|
-
receipts:
|
|
37120
|
-
searches:
|
|
37121
|
-
ledger:
|
|
37343
|
+
agentsDir: join2(directory, "agents"),
|
|
37344
|
+
config: join2(directory, "config.json"),
|
|
37345
|
+
keystore: join2(directory, "keystore.json"),
|
|
37346
|
+
receipts: join2(directory, "receipts.jsonl"),
|
|
37347
|
+
searches: join2(directory, "searches.jsonl"),
|
|
37348
|
+
ledger: join2(directory, "spend-ledger.json")
|
|
37122
37349
|
};
|
|
37123
37350
|
}
|
|
37124
37351
|
var getAgentCashPaths = getVapiPaths;
|
|
37352
|
+
async function migrateLegacyVapiHome(options = {}) {
|
|
37353
|
+
const target = getVapiPaths(options.targetDirectory);
|
|
37354
|
+
const legacyDirectory = options.legacyDirectory ?? join2(homedir(), ".vapi", "agent-cash");
|
|
37355
|
+
if (legacyDirectory === target.directory || !await pathExists(legacyDirectory)) return [];
|
|
37356
|
+
await mkdir2(target.directory, { recursive: true, mode: 448 });
|
|
37357
|
+
const copied = [];
|
|
37358
|
+
for (const filename of [
|
|
37359
|
+
"config.json",
|
|
37360
|
+
"keystore.json",
|
|
37361
|
+
"receipts.jsonl",
|
|
37362
|
+
"searches.jsonl",
|
|
37363
|
+
"spend-ledger.json"
|
|
37364
|
+
]) {
|
|
37365
|
+
const source = join2(legacyDirectory, filename);
|
|
37366
|
+
const destination = join2(target.directory, filename);
|
|
37367
|
+
try {
|
|
37368
|
+
const copy = async () => await copyFile(source, destination, constants.COPYFILE_EXCL);
|
|
37369
|
+
if (filename === "receipts.jsonl") {
|
|
37370
|
+
await withFileLock(`${destination}.lock`, copy, {
|
|
37371
|
+
lockedMessage: "The receipt journal is busy. Wait for the other vapi command to finish, then retry."
|
|
37372
|
+
});
|
|
37373
|
+
} else {
|
|
37374
|
+
await copy();
|
|
37375
|
+
}
|
|
37376
|
+
copied.push(destination);
|
|
37377
|
+
} catch (error61) {
|
|
37378
|
+
if (isMissingFile(error61) || isAlreadyExists(error61)) continue;
|
|
37379
|
+
throw error61;
|
|
37380
|
+
}
|
|
37381
|
+
}
|
|
37382
|
+
if (copied.length > 0) {
|
|
37383
|
+
const message = `Migrated legacy vAPI wallet files from ${legacyDirectory} to ${target.directory}; originals were kept.`;
|
|
37384
|
+
(options.notice ?? ((value) => process.stderr.write(`${value}
|
|
37385
|
+
`)))(message);
|
|
37386
|
+
}
|
|
37387
|
+
return copied;
|
|
37388
|
+
}
|
|
37389
|
+
var LEGACY_REGISTRY_HOSTS = /* @__PURE__ */ new Map([
|
|
37390
|
+
["console.vapinetwork.ai", "api.vapinetwork.ai"],
|
|
37391
|
+
["console-staging.vapinetwork.ai", "api-staging.vapinetwork.ai"]
|
|
37392
|
+
]);
|
|
37393
|
+
var LEGACY_REGISTRY_PATHS = /* @__PURE__ */ new Map([
|
|
37394
|
+
["/api/network/services", "/api/call/services"],
|
|
37395
|
+
["/api/marketplace/discovery", "/api/call/discovery"]
|
|
37396
|
+
]);
|
|
37397
|
+
var REGISTRY_DOMAIN = "vapinetwork.ai";
|
|
37398
|
+
function rewriteLegacyRegistryUrl(value) {
|
|
37399
|
+
let url2;
|
|
37400
|
+
try {
|
|
37401
|
+
url2 = new URL(value);
|
|
37402
|
+
} catch {
|
|
37403
|
+
return value;
|
|
37404
|
+
}
|
|
37405
|
+
let rewritten = false;
|
|
37406
|
+
const host = LEGACY_REGISTRY_HOSTS.get(url2.hostname.toLowerCase());
|
|
37407
|
+
if (host) {
|
|
37408
|
+
url2.hostname = host;
|
|
37409
|
+
rewritten = true;
|
|
37410
|
+
}
|
|
37411
|
+
const hostname3 = url2.hostname.toLowerCase();
|
|
37412
|
+
if (hostname3 === REGISTRY_DOMAIN || hostname3.endsWith(`.${REGISTRY_DOMAIN}`)) {
|
|
37413
|
+
const path = LEGACY_REGISTRY_PATHS.get(url2.pathname.replace(/\/+$/, ""));
|
|
37414
|
+
if (path) {
|
|
37415
|
+
url2.pathname = path;
|
|
37416
|
+
rewritten = true;
|
|
37417
|
+
}
|
|
37418
|
+
}
|
|
37419
|
+
return rewritten ? url2.href : value;
|
|
37420
|
+
}
|
|
37421
|
+
function rewriteLegacyRegistryUrls(config2) {
|
|
37422
|
+
const changes = [];
|
|
37423
|
+
const rewrite = (field, from15) => {
|
|
37424
|
+
const to = rewriteLegacyRegistryUrl(from15);
|
|
37425
|
+
if (to !== from15) changes.push({ field, from: from15, to });
|
|
37426
|
+
return to;
|
|
37427
|
+
};
|
|
37428
|
+
const next = {
|
|
37429
|
+
...config2,
|
|
37430
|
+
discoveryUrl: rewrite("discoveryUrl", config2.discoveryUrl),
|
|
37431
|
+
marketplaceDiscoveryUrl: rewrite("marketplaceDiscoveryUrl", config2.marketplaceDiscoveryUrl),
|
|
37432
|
+
...config2.registryFallbacks ? {
|
|
37433
|
+
registryFallbacks: config2.registryFallbacks.map((fallback, index2) => ({
|
|
37434
|
+
discoveryUrl: rewrite(
|
|
37435
|
+
`registryFallbacks[${index2}].discoveryUrl`,
|
|
37436
|
+
fallback.discoveryUrl
|
|
37437
|
+
),
|
|
37438
|
+
marketplaceDiscoveryUrl: rewrite(
|
|
37439
|
+
`registryFallbacks[${index2}].marketplaceDiscoveryUrl`,
|
|
37440
|
+
fallback.marketplaceDiscoveryUrl
|
|
37441
|
+
)
|
|
37442
|
+
}))
|
|
37443
|
+
} : {}
|
|
37444
|
+
};
|
|
37445
|
+
return changes.length === 0 ? { config: config2, changes } : { config: next, changes };
|
|
37446
|
+
}
|
|
37447
|
+
function formatLegacyRegistryRewrites(changes) {
|
|
37448
|
+
return [
|
|
37449
|
+
"Rewrote retired vAPI registry URLs in the local config:",
|
|
37450
|
+
...changes.map((change) => ` ${change.field}: ${change.from} -> ${change.to}`)
|
|
37451
|
+
].join("\n");
|
|
37452
|
+
}
|
|
37453
|
+
function getDefaultConfig(source = process.env, options = {}) {
|
|
37454
|
+
const registry2 = registryEndpoints(source.VAPI_REGISTRY_URL?.trim() || DEFAULT_REGISTRY_URL);
|
|
37455
|
+
const requested = new Set(options.networks ?? ["base"]);
|
|
37456
|
+
const networks = {};
|
|
37457
|
+
if (requested.has("base") || requested.has(BASE_MAINNET_CAIP2)) {
|
|
37458
|
+
networks[BASE_MAINNET_CAIP2] = {
|
|
37459
|
+
rpcUrl: source.BASE_RPC_URL?.trim() || NETWORKS[BASE_MAINNET_CAIP2].publicRpcUrl,
|
|
37460
|
+
usdc: NETWORKS[BASE_MAINNET_CAIP2].usdc
|
|
37461
|
+
};
|
|
37462
|
+
}
|
|
37463
|
+
const arcMainnetRpcUrl = source.ARC_RPC_URL?.trim();
|
|
37464
|
+
if (arcMainnetRpcUrl || requested.has("arc") || requested.has(ARC_MAINNET_CAIP2)) {
|
|
37465
|
+
networks[ARC_MAINNET_CAIP2] = {
|
|
37466
|
+
rpcUrl: arcMainnetRpcUrl || NETWORKS[ARC_MAINNET_CAIP2].publicRpcUrl,
|
|
37467
|
+
usdc: NETWORKS[ARC_MAINNET_CAIP2].usdc
|
|
37468
|
+
};
|
|
37469
|
+
}
|
|
37470
|
+
const arcTestnetRpcUrl = source.ARC_TESTNET_RPC_URL?.trim();
|
|
37471
|
+
if (arcTestnetRpcUrl || requested.has("arc-testnet") || requested.has(ARC_TESTNET_CAIP2)) {
|
|
37472
|
+
if (!arcTestnetRpcUrl) {
|
|
37473
|
+
throw new Error(
|
|
37474
|
+
"Arc testnet RPC is required. Set ARC_TESTNET_RPC_URL before enabling Arc testnet."
|
|
37475
|
+
);
|
|
37476
|
+
}
|
|
37477
|
+
networks[ARC_TESTNET_CAIP2] = {
|
|
37478
|
+
rpcUrl: arcTestnetRpcUrl,
|
|
37479
|
+
usdc: NETWORKS[ARC_TESTNET_CAIP2].usdc
|
|
37480
|
+
};
|
|
37481
|
+
}
|
|
37482
|
+
const solanaRpcUrl = source.SOLANA_RPC_URL?.trim();
|
|
37483
|
+
if (solanaRpcUrl || requested.has("solana") || requested.has(SOLANA_MAINNET_CAIP2)) {
|
|
37484
|
+
networks[SOLANA_MAINNET_CAIP2] = {
|
|
37485
|
+
rpcUrl: solanaRpcUrl || NETWORKS[SOLANA_MAINNET_CAIP2].publicRpcUrl,
|
|
37486
|
+
usdc: NETWORKS[SOLANA_MAINNET_CAIP2].usdc
|
|
37487
|
+
};
|
|
37488
|
+
}
|
|
37489
|
+
return {
|
|
37490
|
+
discoveryUrl: source.VAPI_DISCOVERY_URL?.trim() || registry2.discoveryUrl,
|
|
37491
|
+
marketplaceDiscoveryUrl: source.VAPI_MARKETPLACE_DISCOVERY_URL?.trim() || registry2.marketplaceDiscoveryUrl,
|
|
37492
|
+
registryFallbacks: DEFAULT_REGISTRY_FALLBACKS.map((fallback) => ({ ...fallback })),
|
|
37493
|
+
allowPrivateNetwork: false,
|
|
37494
|
+
networks,
|
|
37495
|
+
spendCaps: { ...DEFAULT_SPEND_CAPS }
|
|
37496
|
+
};
|
|
37497
|
+
}
|
|
37498
|
+
async function loadConfig(path = getVapiPaths().config, source = process.env, options = {}) {
|
|
37499
|
+
if (!process.env.VAPI_HOME?.trim() && path === getVapiPaths(join2(homedir(), ".vapi")).config) {
|
|
37500
|
+
await migrateLegacyVapiHome({ targetDirectory: join2(homedir(), ".vapi") });
|
|
37501
|
+
}
|
|
37502
|
+
let raw;
|
|
37503
|
+
try {
|
|
37504
|
+
raw = await readFile2(path, "utf8");
|
|
37505
|
+
} catch (error61) {
|
|
37506
|
+
if (isMissingFile(error61)) return getDefaultConfig(source);
|
|
37507
|
+
throw error61;
|
|
37508
|
+
}
|
|
37509
|
+
const parsed = configSchema.parse(JSON.parse(raw));
|
|
37510
|
+
parsed.registryFallbacks ??= DEFAULT_REGISTRY_FALLBACKS.map((fallback) => ({ ...fallback }));
|
|
37511
|
+
const migrated = rewriteLegacyRegistryUrls(parsed);
|
|
37512
|
+
const config2 = migrated.config;
|
|
37513
|
+
if (migrated.changes.length > 0 && options.notice) {
|
|
37514
|
+
options.notice(
|
|
37515
|
+
`${formatLegacyRegistryRewrites(migrated.changes)}
|
|
37516
|
+
Run vapi init to write the new URLs to ${path}.`
|
|
37517
|
+
);
|
|
37518
|
+
}
|
|
37519
|
+
const baseRpcUrl = source.BASE_RPC_URL?.trim();
|
|
37520
|
+
const arcMainnetRpcUrl = source.ARC_RPC_URL?.trim();
|
|
37521
|
+
const arcTestnetRpcUrl = source.ARC_TESTNET_RPC_URL?.trim();
|
|
37522
|
+
const solanaRpcUrl = source.SOLANA_RPC_URL?.trim();
|
|
37523
|
+
const discoveryUrl = source.VAPI_DISCOVERY_URL?.trim();
|
|
37524
|
+
const marketplaceDiscoveryUrl = source.VAPI_MARKETPLACE_DISCOVERY_URL?.trim();
|
|
37525
|
+
const registryUrl = source.VAPI_REGISTRY_URL?.trim();
|
|
37526
|
+
if (config2.networks[BASE_MAINNET_CAIP2] && baseRpcUrl) {
|
|
37527
|
+
config2.networks[BASE_MAINNET_CAIP2].rpcUrl = baseRpcUrl;
|
|
37528
|
+
}
|
|
37529
|
+
if (arcMainnetRpcUrl) {
|
|
37530
|
+
const existing = config2.networks[ARC_MAINNET_CAIP2];
|
|
37531
|
+
config2.networks[ARC_MAINNET_CAIP2] = {
|
|
37532
|
+
...existing ?? { usdc: NETWORKS[ARC_MAINNET_CAIP2].usdc },
|
|
37533
|
+
rpcUrl: arcMainnetRpcUrl
|
|
37534
|
+
};
|
|
37535
|
+
}
|
|
37536
|
+
if (arcTestnetRpcUrl) {
|
|
37537
|
+
const existing = config2.networks[ARC_TESTNET_CAIP2];
|
|
37538
|
+
config2.networks[ARC_TESTNET_CAIP2] = {
|
|
37539
|
+
...existing ?? { usdc: NETWORKS[ARC_TESTNET_CAIP2].usdc },
|
|
37540
|
+
rpcUrl: arcTestnetRpcUrl
|
|
37541
|
+
};
|
|
37542
|
+
}
|
|
37543
|
+
if (solanaRpcUrl) {
|
|
37544
|
+
config2.networks[SOLANA_MAINNET_CAIP2] = {
|
|
37545
|
+
rpcUrl: solanaRpcUrl,
|
|
37546
|
+
usdc: config2.networks[SOLANA_MAINNET_CAIP2]?.usdc ?? NETWORKS[SOLANA_MAINNET_CAIP2].usdc
|
|
37547
|
+
};
|
|
37548
|
+
}
|
|
37549
|
+
if (registryUrl) {
|
|
37550
|
+
const registry2 = registryEndpoints(registryUrl);
|
|
37551
|
+
config2.discoveryUrl = registry2.discoveryUrl;
|
|
37552
|
+
config2.marketplaceDiscoveryUrl = registry2.marketplaceDiscoveryUrl;
|
|
37553
|
+
}
|
|
37554
|
+
if (discoveryUrl) config2.discoveryUrl = discoveryUrl;
|
|
37555
|
+
if (marketplaceDiscoveryUrl) config2.marketplaceDiscoveryUrl = marketplaceDiscoveryUrl;
|
|
37556
|
+
for (const [network, configured] of Object.entries(config2.networks)) {
|
|
37557
|
+
configured.rpcUrl = configured.rpcUrl.trim();
|
|
37558
|
+
configured.usdc = configured.usdc.trim();
|
|
37559
|
+
if (isSolanaNetwork(network) && configured.usdc !== NETWORKS[SOLANA_MAINNET_CAIP2].usdc) {
|
|
37560
|
+
throw new Error(`Network ${network} must use canonical Solana mainnet USDC.`);
|
|
37561
|
+
}
|
|
37562
|
+
if (!configured.rpcUrl) delete config2.networks[network];
|
|
37563
|
+
}
|
|
37564
|
+
return config2;
|
|
37565
|
+
}
|
|
37566
|
+
function registryEndpoints(baseUrl) {
|
|
37567
|
+
const base = new URL(baseUrl);
|
|
37568
|
+
base.search = "";
|
|
37569
|
+
base.hash = "";
|
|
37570
|
+
const prefix = base.pathname === "/" ? "" : base.pathname.replace(/\/+$/, "");
|
|
37571
|
+
const discovery = new URL(base);
|
|
37572
|
+
discovery.pathname = `${prefix}/api/call/services`;
|
|
37573
|
+
const marketplace = new URL(base);
|
|
37574
|
+
marketplace.pathname = `${prefix}/api/call/discovery`;
|
|
37575
|
+
return { discoveryUrl: discovery.href, marketplaceDiscoveryUrl: marketplace.href };
|
|
37576
|
+
}
|
|
37577
|
+
async function pathExists(path) {
|
|
37578
|
+
try {
|
|
37579
|
+
await stat2(path);
|
|
37580
|
+
return true;
|
|
37581
|
+
} catch (error61) {
|
|
37582
|
+
if (isMissingFile(error61)) return false;
|
|
37583
|
+
throw error61;
|
|
37584
|
+
}
|
|
37585
|
+
}
|
|
37125
37586
|
function isMissingFile(error61) {
|
|
37126
37587
|
return error61 instanceof Error && "code" in error61 && error61.code === "ENOENT";
|
|
37127
37588
|
}
|
|
37589
|
+
function isAlreadyExists(error61) {
|
|
37590
|
+
return error61 instanceof Error && "code" in error61 && error61.code === "EEXIST";
|
|
37591
|
+
}
|
|
37128
37592
|
|
|
37129
37593
|
// src/audit.ts
|
|
37130
37594
|
var AUDIT_FILE = "audit.log";
|
|
37131
37595
|
function auditLogPath(home) {
|
|
37132
|
-
return
|
|
37596
|
+
return join3(home, AUDIT_FILE);
|
|
37133
37597
|
}
|
|
37134
37598
|
async function appendAudit(home, record2, options = {}) {
|
|
37135
37599
|
const entry = {
|
|
37136
37600
|
time: (options.now?.() ?? /* @__PURE__ */ new Date()).toISOString(),
|
|
37137
37601
|
event: record2.event,
|
|
37602
|
+
...record2.run === void 0 ? {} : { run: record2.run },
|
|
37138
37603
|
...record2.wallet === void 0 ? {} : { wallet: record2.wallet },
|
|
37139
37604
|
...record2.owner === void 0 ? {} : { owner: record2.owner },
|
|
37140
37605
|
tty: record2.tty,
|
|
@@ -37142,15 +37607,19 @@ async function appendAudit(home, record2, options = {}) {
|
|
|
37142
37607
|
...record2.detail === void 0 ? {} : { detail: record2.detail }
|
|
37143
37608
|
};
|
|
37144
37609
|
const path = auditLogPath(home);
|
|
37145
|
-
await
|
|
37610
|
+
await mkdir3(home, { recursive: true, mode: 448 });
|
|
37146
37611
|
await appendFile(path, `${JSON.stringify(entry)}
|
|
37147
37612
|
`, { encoding: "utf8", mode: 384 });
|
|
37148
37613
|
await chmod(path, 384);
|
|
37149
37614
|
return entry;
|
|
37150
37615
|
}
|
|
37151
37616
|
|
|
37617
|
+
// src/device.ts
|
|
37618
|
+
var DEVICE_NAME_PATTERN = /^[a-z0-9][a-z0-9-]{0,31}$/;
|
|
37619
|
+
|
|
37152
37620
|
// src/agent-link.ts
|
|
37153
37621
|
var AGENT_LINK_REVOKED_MESSAGE = "This agent's link was revoked or has expired. Run vapi login to link it again.";
|
|
37622
|
+
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
37623
|
var AgentLinkError = class extends Error {
|
|
37155
37624
|
constructor(code, message) {
|
|
37156
37625
|
super(message);
|
|
@@ -37184,8 +37653,8 @@ async function agentFetch(args, url2, init) {
|
|
|
37184
37653
|
}
|
|
37185
37654
|
async function resolveAgentAccessToken(args, options) {
|
|
37186
37655
|
await args.wallets.reload();
|
|
37187
|
-
const
|
|
37188
|
-
if (
|
|
37656
|
+
const link2 = args.wallets.entry(args.wallet)?.link;
|
|
37657
|
+
if (link2 === void 0 || !args.secrets.available) throw notLinkedError();
|
|
37189
37658
|
const accounts = agentSecretAccounts(args.wallet);
|
|
37190
37659
|
const tokens = parseTokens(await safeGetSecret(args.secrets, accounts.tokens));
|
|
37191
37660
|
if (tokens === void 0) throw notLinkedError();
|
|
@@ -37193,7 +37662,7 @@ async function resolveAgentAccessToken(args, options) {
|
|
|
37193
37662
|
if (options.rejectedAccessToken === void 0 && tokens.expiresAt - 6e4 > now()) {
|
|
37194
37663
|
return tokens.accessToken;
|
|
37195
37664
|
}
|
|
37196
|
-
return await withAgentCredentialLock(args.wallets,
|
|
37665
|
+
return await withAgentCredentialLock(args.wallets, link2.clientId, async () => {
|
|
37197
37666
|
await args.wallets.reload();
|
|
37198
37667
|
const currentLink = args.wallets.entry(args.wallet)?.link;
|
|
37199
37668
|
if (currentLink === void 0) throw notLinkedError();
|
|
@@ -37206,6 +37675,9 @@ async function resolveAgentAccessToken(args, options) {
|
|
|
37206
37675
|
if (options.rejectedAccessToken === void 0 && current.expiresAt - 6e4 > now()) {
|
|
37207
37676
|
return current.accessToken;
|
|
37208
37677
|
}
|
|
37678
|
+
if (current.refreshToken === void 0) {
|
|
37679
|
+
throw new AgentLinkError("not_linked", REMOTE_AGENT_TOKEN_EXPIRED_MESSAGE);
|
|
37680
|
+
}
|
|
37209
37681
|
const response = await safeFetch(
|
|
37210
37682
|
args.fetchImpl ?? publicFetch,
|
|
37211
37683
|
apiEndpoint(currentLink.apiBase, "/oauth/token"),
|
|
@@ -37239,12 +37711,12 @@ async function resolveAgentAccessToken(args, options) {
|
|
|
37239
37711
|
async function withAgentCredentialLock(wallets, clientId, operation) {
|
|
37240
37712
|
const digest = createHash("sha256").update(clientId).digest("hex").slice(0, 24);
|
|
37241
37713
|
const lockPath = `${wallets.registryPath}.${digest}.agent-link.lock`;
|
|
37242
|
-
await
|
|
37714
|
+
await mkdir4(dirname3(lockPath), { recursive: true, mode: 448 });
|
|
37243
37715
|
const deadline = Date.now() + 3e4;
|
|
37244
37716
|
let handle;
|
|
37245
37717
|
while (!handle) {
|
|
37246
37718
|
try {
|
|
37247
|
-
handle = await
|
|
37719
|
+
handle = await open3(lockPath, "wx", 384);
|
|
37248
37720
|
} catch (error61) {
|
|
37249
37721
|
if (!(error61 instanceof Error && "code" in error61 && error61.code === "EEXIST")) throw error61;
|
|
37250
37722
|
await removeStaleCredentialLock(lockPath);
|
|
@@ -37258,13 +37730,13 @@ async function withAgentCredentialLock(wallets, clientId, operation) {
|
|
|
37258
37730
|
return await operation();
|
|
37259
37731
|
} finally {
|
|
37260
37732
|
await handle.close();
|
|
37261
|
-
await
|
|
37733
|
+
await unlink2(lockPath).catch(() => void 0);
|
|
37262
37734
|
}
|
|
37263
37735
|
}
|
|
37264
37736
|
async function removeStaleCredentialLock(path) {
|
|
37265
37737
|
try {
|
|
37266
|
-
const metadata = await
|
|
37267
|
-
if (Date.now() - metadata.mtimeMs > 5 * 6e4) await
|
|
37738
|
+
const metadata = await stat3(path);
|
|
37739
|
+
if (Date.now() - metadata.mtimeMs > 5 * 6e4) await unlink2(path);
|
|
37268
37740
|
} catch {
|
|
37269
37741
|
}
|
|
37270
37742
|
}
|
|
@@ -37293,12 +37765,18 @@ function parseTokens(value) {
|
|
|
37293
37765
|
if (!isRecord(parsed)) return void 0;
|
|
37294
37766
|
const accessToken = stringField(parsed, "accessToken");
|
|
37295
37767
|
const refreshToken = stringField(parsed, "refreshToken");
|
|
37768
|
+
const refreshable = parsed.refreshable;
|
|
37296
37769
|
const expiresAt = numberField(parsed, "expiresAt");
|
|
37297
37770
|
const scopes = parsed.scopes;
|
|
37298
|
-
if (accessToken === void 0 ||
|
|
37771
|
+
if (accessToken === void 0 || expiresAt === void 0 || !Array.isArray(scopes) || scopes.some((scope) => typeof scope !== "string") || (refreshToken === void 0 ? refreshable !== false : refreshable === false)) {
|
|
37299
37772
|
return void 0;
|
|
37300
37773
|
}
|
|
37301
|
-
return {
|
|
37774
|
+
return {
|
|
37775
|
+
accessToken,
|
|
37776
|
+
...refreshToken === void 0 ? {} : { refreshToken },
|
|
37777
|
+
expiresAt,
|
|
37778
|
+
scopes
|
|
37779
|
+
};
|
|
37302
37780
|
}
|
|
37303
37781
|
function withBearer(init, accessToken) {
|
|
37304
37782
|
const headers = new Headers(init?.headers);
|
|
@@ -37364,8 +37842,92 @@ function splitScopes(scope) {
|
|
|
37364
37842
|
}
|
|
37365
37843
|
|
|
37366
37844
|
// src/ceiling.ts
|
|
37367
|
-
import { randomBytes as
|
|
37368
|
-
|
|
37845
|
+
import { randomBytes as randomBytes7 } from "node:crypto";
|
|
37846
|
+
|
|
37847
|
+
// src/account-movement-lock.ts
|
|
37848
|
+
import { randomBytes as randomBytes3 } from "node:crypto";
|
|
37849
|
+
import { mkdir as mkdir5, open as open4, readFile as readFile4, stat as stat4, unlink as unlink3 } from "node:fs/promises";
|
|
37850
|
+
import { join as join4 } from "node:path";
|
|
37851
|
+
var AccountMovementLockedError = class extends Error {
|
|
37852
|
+
constructor(account) {
|
|
37853
|
+
super(`Another capital movement is already running for account ${account}.`);
|
|
37854
|
+
this.account = account;
|
|
37855
|
+
this.name = "AccountMovementLockedError";
|
|
37856
|
+
}
|
|
37857
|
+
account;
|
|
37858
|
+
code = "account_movement_locked";
|
|
37859
|
+
};
|
|
37860
|
+
async function withAccountMovementLock(home, account, timeoutMs, operation, signal) {
|
|
37861
|
+
const directory = join4(home, "movements");
|
|
37862
|
+
await mkdir5(directory, { recursive: true, mode: 448 });
|
|
37863
|
+
const path = join4(directory, `.${account}.lock`);
|
|
37864
|
+
const deadline = Date.now() + (timeoutMs ?? 2e3);
|
|
37865
|
+
const owner = JSON.stringify({ pid: process.pid, token: randomBytes3(16).toString("hex") });
|
|
37866
|
+
let handle;
|
|
37867
|
+
while (handle === void 0) {
|
|
37868
|
+
signal?.throwIfAborted();
|
|
37869
|
+
try {
|
|
37870
|
+
handle = await open4(path, "wx", 384);
|
|
37871
|
+
await handle.writeFile(owner, "utf8");
|
|
37872
|
+
await handle.sync();
|
|
37873
|
+
} catch (error61) {
|
|
37874
|
+
if (!(error61 instanceof Error && "code" in error61 && error61.code === "EEXIST")) throw error61;
|
|
37875
|
+
await removeStaleLock2(path);
|
|
37876
|
+
if (Date.now() >= deadline) throw new AccountMovementLockedError(account);
|
|
37877
|
+
await new Promise((resolve) => setTimeout(resolve, 20));
|
|
37878
|
+
}
|
|
37879
|
+
}
|
|
37880
|
+
const ownedHandle = handle;
|
|
37881
|
+
const heartbeat = setInterval(() => {
|
|
37882
|
+
const time3 = /* @__PURE__ */ new Date();
|
|
37883
|
+
void ownedHandle.utimes(time3, time3).catch(() => void 0);
|
|
37884
|
+
}, 3e4);
|
|
37885
|
+
heartbeat.unref();
|
|
37886
|
+
try {
|
|
37887
|
+
return await operation();
|
|
37888
|
+
} finally {
|
|
37889
|
+
clearInterval(heartbeat);
|
|
37890
|
+
await ownedHandle.close();
|
|
37891
|
+
await unlinkOwnedLock(path, owner);
|
|
37892
|
+
}
|
|
37893
|
+
}
|
|
37894
|
+
async function removeStaleLock2(path) {
|
|
37895
|
+
try {
|
|
37896
|
+
const metadata = await stat4(path);
|
|
37897
|
+
if (Date.now() - metadata.mtimeMs <= 12e4) return;
|
|
37898
|
+
const owner = await readFile4(path, "utf8").catch(() => "");
|
|
37899
|
+
const pid = lockOwnerPid2(owner);
|
|
37900
|
+
if (pid !== void 0 && processIsAlive2(pid)) return;
|
|
37901
|
+
await unlinkOwnedLock(path, owner);
|
|
37902
|
+
} catch (error61) {
|
|
37903
|
+
if (!isMissingFile(error61)) throw error61;
|
|
37904
|
+
}
|
|
37905
|
+
}
|
|
37906
|
+
async function unlinkOwnedLock(path, owner) {
|
|
37907
|
+
try {
|
|
37908
|
+
if (await readFile4(path, "utf8") === owner) await unlink3(path);
|
|
37909
|
+
} catch (error61) {
|
|
37910
|
+
if (!isMissingFile(error61)) throw error61;
|
|
37911
|
+
}
|
|
37912
|
+
}
|
|
37913
|
+
function lockOwnerPid2(owner) {
|
|
37914
|
+
try {
|
|
37915
|
+
const value = JSON.parse(owner);
|
|
37916
|
+
if (typeof value !== "object" || value === null) return void 0;
|
|
37917
|
+
const pid = Reflect.get(value, "pid");
|
|
37918
|
+
return typeof pid === "number" && Number.isSafeInteger(pid) && pid > 0 ? pid : void 0;
|
|
37919
|
+
} catch {
|
|
37920
|
+
return void 0;
|
|
37921
|
+
}
|
|
37922
|
+
}
|
|
37923
|
+
function processIsAlive2(pid) {
|
|
37924
|
+
try {
|
|
37925
|
+
process.kill(pid, 0);
|
|
37926
|
+
return true;
|
|
37927
|
+
} catch (error61) {
|
|
37928
|
+
return error61 instanceof Error && "code" in error61 && error61.code === "EPERM";
|
|
37929
|
+
}
|
|
37930
|
+
}
|
|
37369
37931
|
|
|
37370
37932
|
// src/authorization-state.ts
|
|
37371
37933
|
var EIP3009_AUTHORIZATION_STATE_ABI = [
|
|
@@ -37429,9 +37991,47 @@ function missingAuthorization(receipt) {
|
|
|
37429
37991
|
return `Receipt ${receipt.id} records no signed payment authorization, so nothing from that call can settle.`;
|
|
37430
37992
|
}
|
|
37431
37993
|
|
|
37432
|
-
// src/
|
|
37433
|
-
import {
|
|
37434
|
-
import {
|
|
37994
|
+
// src/movement.ts
|
|
37995
|
+
import { randomBytes as randomBytes6 } from "node:crypto";
|
|
37996
|
+
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";
|
|
37997
|
+
import { join as join5 } from "node:path";
|
|
37998
|
+
|
|
37999
|
+
// src/allocation.ts
|
|
38000
|
+
var AllocationError = class extends Error {
|
|
38001
|
+
constructor(code, message) {
|
|
38002
|
+
super(message);
|
|
38003
|
+
this.code = code;
|
|
38004
|
+
this.name = "AllocationError";
|
|
38005
|
+
}
|
|
38006
|
+
code;
|
|
38007
|
+
};
|
|
38008
|
+
function parseUsdCents(value) {
|
|
38009
|
+
const raw = String(value);
|
|
38010
|
+
if (!/^\d+(?:\.\d{1,2})?$/.test(raw)) {
|
|
38011
|
+
throw new AllocationError("invalid_amount", "Enter a positive USDC amount in whole cents.");
|
|
38012
|
+
}
|
|
38013
|
+
const [whole = "0", fraction = ""] = raw.split(".");
|
|
38014
|
+
const cents = BigInt(whole) * 100n + BigInt(fraction.padEnd(2, "0") || "0");
|
|
38015
|
+
if (cents <= 0n) {
|
|
38016
|
+
throw new AllocationError("invalid_amount", "Enter a positive USDC amount in whole cents.");
|
|
38017
|
+
}
|
|
38018
|
+
return cents;
|
|
38019
|
+
}
|
|
38020
|
+
|
|
38021
|
+
// src/movement-open.ts
|
|
38022
|
+
function isOpenMovementLeg(leg, journalEvidence) {
|
|
38023
|
+
if (leg.status === "sent" || leg.status === "cancelled") return false;
|
|
38024
|
+
if (leg.restored === true) return true;
|
|
38025
|
+
if (leg.status === "planned" || leg.status === "unknown") return true;
|
|
38026
|
+
if (leg.status !== "failed") return false;
|
|
38027
|
+
if (leg.retryable === true) return true;
|
|
38028
|
+
return journalEvidence === "signed";
|
|
38029
|
+
}
|
|
38030
|
+
|
|
38031
|
+
// src/spend-policy.ts
|
|
38032
|
+
import { randomBytes as randomBytes4 } from "node:crypto";
|
|
38033
|
+
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";
|
|
38034
|
+
import { dirname as dirname4 } from "node:path";
|
|
37435
38035
|
|
|
37436
38036
|
// src/wallet-name.ts
|
|
37437
38037
|
var DEFAULT_WALLET_NAME = "main";
|
|
@@ -37441,11 +38041,230 @@ var walletNameSchema = external_exports.string().regex(
|
|
|
37441
38041
|
"A wallet name is 1 to 32 characters of lowercase letters, digits and dashes, starting with a letter or digit."
|
|
37442
38042
|
);
|
|
37443
38043
|
|
|
38044
|
+
// src/spend-policy.ts
|
|
38045
|
+
var ledgerSchema = external_exports.object({
|
|
38046
|
+
date: external_exports.string().regex(/^\d{4}-\d{2}-\d{2}$/),
|
|
38047
|
+
spentAtomic: external_exports.string().regex(/^\d+$/)
|
|
38048
|
+
});
|
|
38049
|
+
var spendReservationSchema = external_exports.object({
|
|
38050
|
+
id: external_exports.string().min(1),
|
|
38051
|
+
amountAtomic: external_exports.string().regex(/^\d+$/)
|
|
38052
|
+
});
|
|
38053
|
+
var ledgerRowSchema = ledgerSchema.extend({
|
|
38054
|
+
wallet: walletNameSchema.optional(),
|
|
38055
|
+
reservations: external_exports.array(spendReservationSchema).optional()
|
|
38056
|
+
});
|
|
38057
|
+
var ledgerFileSchema = external_exports.union([
|
|
38058
|
+
external_exports.object({ version: external_exports.literal(1), rows: external_exports.array(ledgerRowSchema) }),
|
|
38059
|
+
ledgerRowSchema
|
|
38060
|
+
]);
|
|
38061
|
+
var SpendCapError = class extends Error {
|
|
38062
|
+
constructor(code, message) {
|
|
38063
|
+
super(message);
|
|
38064
|
+
this.code = code;
|
|
38065
|
+
this.name = "SpendCapError";
|
|
38066
|
+
}
|
|
38067
|
+
code;
|
|
38068
|
+
};
|
|
38069
|
+
function utcDateKey(now = /* @__PURE__ */ new Date()) {
|
|
38070
|
+
return now.toISOString().slice(0, 10);
|
|
38071
|
+
}
|
|
38072
|
+
async function readStoredSpendLedgerRows(path, now) {
|
|
38073
|
+
let raw;
|
|
38074
|
+
try {
|
|
38075
|
+
raw = await readFile5(path, "utf8");
|
|
38076
|
+
} catch (error61) {
|
|
38077
|
+
if (isMissingFile(error61)) return [];
|
|
38078
|
+
throw error61;
|
|
38079
|
+
}
|
|
38080
|
+
const parsed = ledgerFileSchema.parse(JSON.parse(raw));
|
|
38081
|
+
const rows = "rows" in parsed ? parsed.rows : [parsed];
|
|
38082
|
+
const today = utcDateKey(now);
|
|
38083
|
+
return rows.filter((row) => row.date === today).map((row) => ({
|
|
38084
|
+
date: row.date,
|
|
38085
|
+
spentAtomic: row.spentAtomic,
|
|
38086
|
+
wallet: row.wallet ?? DEFAULT_WALLET_NAME,
|
|
38087
|
+
...row.reservations === void 0 ? {} : { reservations: row.reservations }
|
|
38088
|
+
}));
|
|
38089
|
+
}
|
|
38090
|
+
async function reserveSpend(amountAtomic, caps, options) {
|
|
38091
|
+
if (amountAtomic < 0n) {
|
|
38092
|
+
throw new Error("Spend amount cannot be negative.");
|
|
38093
|
+
}
|
|
38094
|
+
const perCallAtomic = parseAtomicCap(caps.perCallAtomic, "per-call");
|
|
38095
|
+
const perDayAtomic = parseAtomicCap(caps.perDayAtomic, "per-day");
|
|
38096
|
+
if (amountAtomic > perCallAtomic) {
|
|
38097
|
+
throw new SpendCapError(
|
|
38098
|
+
"per_call_cap_exceeded",
|
|
38099
|
+
`Payment quote ${amountAtomic} atomic USDC exceeds the per-call cap ${perCallAtomic}. Refusing to sign.`
|
|
38100
|
+
);
|
|
38101
|
+
}
|
|
38102
|
+
const ledgerPath = options?.ledgerPath ?? getAgentCashPaths().ledger;
|
|
38103
|
+
const now = options?.now ?? /* @__PURE__ */ new Date();
|
|
38104
|
+
const wallet = options?.wallet ?? DEFAULT_WALLET_NAME;
|
|
38105
|
+
return await withLedgerLock(ledgerPath, async () => {
|
|
38106
|
+
const rows = await readStoredSpendLedgerRows(ledgerPath, now);
|
|
38107
|
+
const current = rows.find((row) => row.wallet === wallet);
|
|
38108
|
+
if (options?.reservationId !== void 0) {
|
|
38109
|
+
if (current?.reservations?.some((reservation) => reservation.id === options.reservationId)) {
|
|
38110
|
+
throw new Error(`Spend reservation ${options.reservationId} already exists.`);
|
|
38111
|
+
}
|
|
38112
|
+
}
|
|
38113
|
+
const spentAtomic = current?.spentAtomic ?? "0";
|
|
38114
|
+
const nextSpent = BigInt(spentAtomic) + amountAtomic;
|
|
38115
|
+
if (nextSpent > perDayAtomic) {
|
|
38116
|
+
throw new SpendCapError(
|
|
38117
|
+
"per_day_cap_exceeded",
|
|
38118
|
+
`Payment quote ${amountAtomic} atomic USDC would raise today's spend to ${nextSpent} for wallet ${wallet}, above the per-day cap ${perDayAtomic}. Refusing to sign.`
|
|
38119
|
+
);
|
|
38120
|
+
}
|
|
38121
|
+
const nextRow = {
|
|
38122
|
+
date: utcDateKey(now),
|
|
38123
|
+
spentAtomic: nextSpent.toString(),
|
|
38124
|
+
wallet,
|
|
38125
|
+
...(current?.reservations?.length ?? 0) === 0 && options?.reservationId === void 0 ? {} : {
|
|
38126
|
+
reservations: [
|
|
38127
|
+
...current?.reservations ?? [],
|
|
38128
|
+
...options?.reservationId === void 0 ? [] : [{ id: options.reservationId, amountAtomic: amountAtomic.toString() }]
|
|
38129
|
+
]
|
|
38130
|
+
}
|
|
38131
|
+
};
|
|
38132
|
+
const nextRows = [...rows.filter((row) => row.wallet !== wallet), nextRow].sort(
|
|
38133
|
+
(a, b) => a.wallet.localeCompare(b.wallet)
|
|
38134
|
+
);
|
|
38135
|
+
await writeLedgerAtomically(ledgerPath, nextRows);
|
|
38136
|
+
return { date: nextRow.date, spentAtomic: nextRow.spentAtomic };
|
|
38137
|
+
});
|
|
38138
|
+
}
|
|
38139
|
+
async function releaseSpend(amountAtomic, options) {
|
|
38140
|
+
if (amountAtomic < 0n) {
|
|
38141
|
+
throw new Error("Spend amount cannot be negative.");
|
|
38142
|
+
}
|
|
38143
|
+
const ledgerPath = options.ledgerPath ?? getAgentCashPaths().ledger;
|
|
38144
|
+
const now = options.now ?? /* @__PURE__ */ new Date();
|
|
38145
|
+
const wallet = options.wallet ?? DEFAULT_WALLET_NAME;
|
|
38146
|
+
return await withLedgerLock(ledgerPath, async () => {
|
|
38147
|
+
const today = utcDateKey(now);
|
|
38148
|
+
const rows = await readStoredSpendLedgerRows(ledgerPath, now);
|
|
38149
|
+
const current = rows.find((row) => row.wallet === wallet);
|
|
38150
|
+
if (options.reservedOn !== today) {
|
|
38151
|
+
return { date: today, spentAtomic: current?.spentAtomic ?? "0" };
|
|
38152
|
+
}
|
|
38153
|
+
const reservation = options.reservationId === void 0 ? void 0 : current?.reservations?.find((candidate) => candidate.id === options.reservationId);
|
|
38154
|
+
if (options.reservationId !== void 0 && reservation === void 0) {
|
|
38155
|
+
return { date: today, spentAtomic: current?.spentAtomic ?? "0" };
|
|
38156
|
+
}
|
|
38157
|
+
if (reservation !== void 0 && reservation.amountAtomic !== amountAtomic.toString()) {
|
|
38158
|
+
throw new Error(`Spend reservation ${options.reservationId} has a different amount.`);
|
|
38159
|
+
}
|
|
38160
|
+
const spentAtomic = BigInt(current?.spentAtomic ?? "0");
|
|
38161
|
+
const releasedAmount = reservation === void 0 ? amountAtomic : BigInt(reservation.amountAtomic);
|
|
38162
|
+
const nextSpent = spentAtomic > releasedAmount ? spentAtomic - releasedAmount : 0n;
|
|
38163
|
+
const remainingReservations = current?.reservations?.filter(
|
|
38164
|
+
(candidate) => candidate.id !== options.reservationId
|
|
38165
|
+
);
|
|
38166
|
+
const nextRow = {
|
|
38167
|
+
date: today,
|
|
38168
|
+
spentAtomic: nextSpent.toString(),
|
|
38169
|
+
wallet,
|
|
38170
|
+
...remainingReservations === void 0 || remainingReservations.length === 0 ? {} : { reservations: remainingReservations }
|
|
38171
|
+
};
|
|
38172
|
+
const nextRows = [...rows.filter((row) => row.wallet !== wallet), nextRow].sort(
|
|
38173
|
+
(a, b) => a.wallet.localeCompare(b.wallet)
|
|
38174
|
+
);
|
|
38175
|
+
await writeLedgerAtomically(ledgerPath, nextRows);
|
|
38176
|
+
return { date: nextRow.date, spentAtomic: nextRow.spentAtomic };
|
|
38177
|
+
});
|
|
38178
|
+
}
|
|
38179
|
+
function parseAtomicCap(value, label) {
|
|
38180
|
+
if (!/^\d+$/.test(value)) {
|
|
38181
|
+
throw new Error(`Invalid ${label} spend cap ${JSON.stringify(value)}.`);
|
|
38182
|
+
}
|
|
38183
|
+
return BigInt(value);
|
|
38184
|
+
}
|
|
38185
|
+
async function writeLedgerAtomically(path, rows) {
|
|
38186
|
+
const ledger = rows.length === 1 && rows[0].wallet === DEFAULT_WALLET_NAME ? {
|
|
38187
|
+
date: rows[0].date,
|
|
38188
|
+
spentAtomic: rows[0].spentAtomic,
|
|
38189
|
+
...rows[0].reservations === void 0 ? {} : { reservations: rows[0].reservations }
|
|
38190
|
+
} : { version: 1, rows };
|
|
38191
|
+
await mkdir6(dirname4(path), { recursive: true, mode: 448 });
|
|
38192
|
+
const temporaryPath = `${path}.${process.pid}.${randomBytes4(6).toString("hex")}.tmp`;
|
|
38193
|
+
await writeFile2(temporaryPath, `${JSON.stringify(ledger, null, 2)}
|
|
38194
|
+
`, {
|
|
38195
|
+
encoding: "utf8",
|
|
38196
|
+
mode: 384,
|
|
38197
|
+
flag: "wx"
|
|
38198
|
+
});
|
|
38199
|
+
await rename3(temporaryPath, path);
|
|
38200
|
+
}
|
|
38201
|
+
async function withLedgerLock(path, operation) {
|
|
38202
|
+
await mkdir6(dirname4(path), { recursive: true, mode: 448 });
|
|
38203
|
+
const lockPath = `${path}.lock`;
|
|
38204
|
+
const deadline = Date.now() + 2e3;
|
|
38205
|
+
let handle;
|
|
38206
|
+
while (!handle) {
|
|
38207
|
+
try {
|
|
38208
|
+
handle = await open5(lockPath, "wx", 384);
|
|
38209
|
+
} catch (error61) {
|
|
38210
|
+
if (!(error61 instanceof Error && "code" in error61 && error61.code === "EEXIST")) {
|
|
38211
|
+
throw error61;
|
|
38212
|
+
}
|
|
38213
|
+
await removeStaleLock3(lockPath);
|
|
38214
|
+
if (Date.now() >= deadline) {
|
|
38215
|
+
throw new Error("Timed out waiting for the spend ledger lock.");
|
|
38216
|
+
}
|
|
38217
|
+
await new Promise((resolve) => setTimeout(resolve, 20));
|
|
38218
|
+
}
|
|
38219
|
+
}
|
|
38220
|
+
try {
|
|
38221
|
+
return await operation();
|
|
38222
|
+
} finally {
|
|
38223
|
+
await handle.close();
|
|
38224
|
+
await unlink4(lockPath).catch(() => void 0);
|
|
38225
|
+
}
|
|
38226
|
+
}
|
|
38227
|
+
async function removeStaleLock3(path) {
|
|
38228
|
+
try {
|
|
38229
|
+
const metadata = await stat5(path);
|
|
38230
|
+
if (Date.now() - metadata.mtimeMs > 3e4) {
|
|
38231
|
+
await unlink4(path);
|
|
38232
|
+
}
|
|
38233
|
+
} catch (error61) {
|
|
38234
|
+
if (!isMissingFile(error61)) {
|
|
38235
|
+
throw error61;
|
|
38236
|
+
}
|
|
38237
|
+
}
|
|
38238
|
+
}
|
|
38239
|
+
|
|
38240
|
+
// src/transfer-journal.ts
|
|
38241
|
+
import { readFile as readFile7 } from "node:fs/promises";
|
|
38242
|
+
|
|
38243
|
+
// src/receipts.ts
|
|
38244
|
+
import { mkdir as mkdir7, open as open6, readFile as readFile6, rename as rename4, writeFile as writeFile3 } from "node:fs/promises";
|
|
38245
|
+
import { dirname as dirname5 } from "node:path";
|
|
38246
|
+
|
|
38247
|
+
// src/agent-run.ts
|
|
38248
|
+
var RUN_ID_PATTERN = /^run_[0-9a-f]{24}$/;
|
|
38249
|
+
var runRefSchema = external_exports.strictObject({
|
|
38250
|
+
id: external_exports.string().regex(RUN_ID_PATTERN),
|
|
38251
|
+
swarm: external_exports.string().optional(),
|
|
38252
|
+
member: external_exports.string().optional(),
|
|
38253
|
+
parentRunId: external_exports.string().optional()
|
|
38254
|
+
});
|
|
38255
|
+
|
|
37444
38256
|
// src/receipts.ts
|
|
38257
|
+
var RECEIPT_JOURNAL_BUSY_MESSAGE = "The receipt journal is busy. Wait for the other vapi command to finish, then retry.";
|
|
38258
|
+
async function withReceiptJournalLock(path, operation) {
|
|
38259
|
+
return await withFileLock(`${path}.lock`, operation, {
|
|
38260
|
+
lockedMessage: RECEIPT_JOURNAL_BUSY_MESSAGE
|
|
38261
|
+
});
|
|
38262
|
+
}
|
|
37445
38263
|
var durationSchema = external_exports.number().nonnegative().finite();
|
|
37446
38264
|
var receiptSchema = external_exports.strictObject({
|
|
37447
38265
|
id: external_exports.string().min(1),
|
|
37448
38266
|
timestamp: external_exports.iso.datetime(),
|
|
38267
|
+
run: runRefSchema.optional(),
|
|
37449
38268
|
kind: external_exports.literal("transfer").optional(),
|
|
37450
38269
|
wallet: walletNameSchema.optional(),
|
|
37451
38270
|
resourceUrl: external_exports.url(),
|
|
@@ -37552,20 +38371,22 @@ async function appendReceipt(receipt, path = getVapiPaths().receipts, options =
|
|
|
37552
38371
|
const parsed = parseReceipt(
|
|
37553
38372
|
receipt.wallet === void 0 && options.wallet !== void 0 ? { ...receipt, wallet: options.wallet } : receipt
|
|
37554
38373
|
);
|
|
37555
|
-
await
|
|
37556
|
-
|
|
37557
|
-
|
|
37558
|
-
|
|
38374
|
+
await withReceiptJournalLock(path, async () => {
|
|
38375
|
+
await mkdir7(dirname5(path), { recursive: true, mode: 448 });
|
|
38376
|
+
const handle = await open6(path, "a", 384);
|
|
38377
|
+
try {
|
|
38378
|
+
await handle.writeFile(`${JSON.stringify(parsed)}
|
|
37559
38379
|
`, "utf8");
|
|
37560
|
-
|
|
37561
|
-
|
|
37562
|
-
|
|
38380
|
+
} finally {
|
|
38381
|
+
await handle.close();
|
|
38382
|
+
}
|
|
38383
|
+
});
|
|
37563
38384
|
return parsed;
|
|
37564
38385
|
}
|
|
37565
38386
|
async function readReceipts(path = getVapiPaths().receipts, options = {}) {
|
|
37566
38387
|
let raw;
|
|
37567
38388
|
try {
|
|
37568
|
-
raw = await
|
|
38389
|
+
raw = await readFile6(path, "utf8");
|
|
37569
38390
|
} catch (error61) {
|
|
37570
38391
|
if (isMissingFile(error61)) return [];
|
|
37571
38392
|
throw error61;
|
|
@@ -37607,6 +38428,45 @@ function filterReceiptsByWallet(receipts, name) {
|
|
|
37607
38428
|
return receipts.filter((receipt) => receiptWallet(receipt) === name);
|
|
37608
38429
|
}
|
|
37609
38430
|
|
|
38431
|
+
// src/transfer-journal.ts
|
|
38432
|
+
async function readTransferJournal(home, from15, nonce) {
|
|
38433
|
+
let raw;
|
|
38434
|
+
try {
|
|
38435
|
+
raw = await readFile7(getVapiPaths(home).receipts, "utf8");
|
|
38436
|
+
} catch (error61) {
|
|
38437
|
+
if (isMissingFile(error61)) return { signed: false };
|
|
38438
|
+
throw error61;
|
|
38439
|
+
}
|
|
38440
|
+
let latest;
|
|
38441
|
+
let request;
|
|
38442
|
+
for (const [index2, line] of raw.split("\n").entries()) {
|
|
38443
|
+
if (!line.trim()) continue;
|
|
38444
|
+
let receipt;
|
|
38445
|
+
try {
|
|
38446
|
+
receipt = parseReceipt(JSON.parse(line));
|
|
38447
|
+
} catch (error61) {
|
|
38448
|
+
throw new Error(`Invalid receipt on JSONL line ${index2 + 1}.`, { cause: error61 });
|
|
38449
|
+
}
|
|
38450
|
+
if (receiptWallet(receipt) !== from15 || receipt.transfer?.nonce.toLowerCase() !== nonce.toLowerCase()) {
|
|
38451
|
+
continue;
|
|
38452
|
+
}
|
|
38453
|
+
latest = receipt;
|
|
38454
|
+
if (receipt.transfer.request !== void 0) request = receipt;
|
|
38455
|
+
}
|
|
38456
|
+
return {
|
|
38457
|
+
signed: latest !== void 0,
|
|
38458
|
+
...latest === void 0 ? {} : { latest },
|
|
38459
|
+
...request === void 0 ? {} : { request },
|
|
38460
|
+
...request?.transfer?.request?.authorization.validBefore === void 0 ? {} : { validBefore: request.transfer.request.authorization.validBefore },
|
|
38461
|
+
...(latest?.transfer?.reservedOn ?? request?.transfer?.reservedOn) === void 0 ? {} : { reservedOn: latest?.transfer?.reservedOn ?? request?.transfer?.reservedOn }
|
|
38462
|
+
};
|
|
38463
|
+
}
|
|
38464
|
+
|
|
38465
|
+
// src/transfer.ts
|
|
38466
|
+
import { randomBytes as randomBytes5, randomUUID } from "node:crypto";
|
|
38467
|
+
import { mkdir as mkdir9, open as open8, stat as stat7, unlink as unlink6 } from "node:fs/promises";
|
|
38468
|
+
import { dirname as dirname6 } from "node:path";
|
|
38469
|
+
|
|
37610
38470
|
// src/siblings.ts
|
|
37611
38471
|
var SiblingsError = class extends Error {
|
|
37612
38472
|
constructor(code, message, status) {
|
|
@@ -37645,8 +38505,8 @@ async function fetchSiblings(args) {
|
|
|
37645
38505
|
try {
|
|
37646
38506
|
response = await withAgentCredentialLock(args.wallets, `wallet:${args.account}`, async () => {
|
|
37647
38507
|
await args.wallets.reload();
|
|
37648
|
-
const
|
|
37649
|
-
if (
|
|
38508
|
+
const link2 = args.wallets.entry(args.account)?.link;
|
|
38509
|
+
if (link2 === void 0 || new URL(link2.apiBase).origin !== new URL(args.apiBase).origin) {
|
|
37650
38510
|
throw new SiblingsError(
|
|
37651
38511
|
"not_linked",
|
|
37652
38512
|
"The selected account is not linked to this vAPI server."
|
|
@@ -37660,7 +38520,7 @@ async function fetchSiblings(args) {
|
|
|
37660
38520
|
...args.fetchImpl === void 0 ? {} : { fetchImpl: args.fetchImpl },
|
|
37661
38521
|
...args.now === void 0 ? {} : { now: args.now }
|
|
37662
38522
|
},
|
|
37663
|
-
new URL("/api/agents/self/siblings", new URL(
|
|
38523
|
+
new URL("/api/agents/self/siblings", new URL(link2.apiBase).origin).toString(),
|
|
37664
38524
|
{ method: "GET", signal }
|
|
37665
38525
|
);
|
|
37666
38526
|
});
|
|
@@ -37699,6 +38559,20 @@ function invalidResponseError() {
|
|
|
37699
38559
|
return new SiblingsError("invalid_response", "The vAPI siblings response was invalid.");
|
|
37700
38560
|
}
|
|
37701
38561
|
|
|
38562
|
+
// src/wallet-store.ts
|
|
38563
|
+
import {
|
|
38564
|
+
chmod as chmod2,
|
|
38565
|
+
lstat,
|
|
38566
|
+
mkdir as mkdir8,
|
|
38567
|
+
open as open7,
|
|
38568
|
+
readdir as readdir2,
|
|
38569
|
+
readFile as readFile8,
|
|
38570
|
+
rename as rename5,
|
|
38571
|
+
stat as stat6,
|
|
38572
|
+
unlink as unlink5,
|
|
38573
|
+
writeFile as writeFile4
|
|
38574
|
+
} from "node:fs/promises";
|
|
38575
|
+
|
|
37702
38576
|
// ../../node_modules/.pnpm/@solana+errors@5.5.1_typescript@6.0.3/node_modules/@solana/errors/dist/index.node.mjs
|
|
37703
38577
|
var SOLANA_ERROR__BLOCK_HEIGHT_EXCEEDED = 1;
|
|
37704
38578
|
var SOLANA_ERROR__INVALID_NONCE = 2;
|
|
@@ -41618,6 +42492,62 @@ async function signModifyingAndPartialTransactionSigners(transactionMessage, mod
|
|
|
41618
42492
|
}
|
|
41619
42493
|
var o2 = globalThis.TextEncoder;
|
|
41620
42494
|
|
|
42495
|
+
// src/wallet-store.ts
|
|
42496
|
+
var agentLinkSchema = external_exports.object({
|
|
42497
|
+
apiBase: external_exports.string(),
|
|
42498
|
+
clientId: external_exports.string(),
|
|
42499
|
+
owner: external_exports.custom((value) => typeof value === "string" && value.startsWith("0x")),
|
|
42500
|
+
label: external_exports.string(),
|
|
42501
|
+
scopes: external_exports.array(external_exports.string()),
|
|
42502
|
+
linkedAt: external_exports.iso.datetime(),
|
|
42503
|
+
routerBaseUrl: external_exports.string().optional()
|
|
42504
|
+
});
|
|
42505
|
+
var ROUTER_TOPUP_TIERS = [1, 5, 20, 50];
|
|
42506
|
+
var routerRefillSchema = external_exports.object({
|
|
42507
|
+
belowUsd: external_exports.number().finite().min(0),
|
|
42508
|
+
tierUsd: external_exports.union([external_exports.literal(1), external_exports.literal(5), external_exports.literal(20), external_exports.literal(50)])
|
|
42509
|
+
});
|
|
42510
|
+
var ceilingSweepPendingSchema = external_exports.strictObject({
|
|
42511
|
+
owner: external_exports.string().regex(/^0x[0-9a-fA-F]{40}$/),
|
|
42512
|
+
target: external_exports.string().regex(/^0x[0-9a-fA-F]{40}$/).optional(),
|
|
42513
|
+
amountAtomic: external_exports.string().regex(/^\d+$/),
|
|
42514
|
+
nonce: external_exports.string().regex(/^0x[0-9a-fA-F]{64}$/),
|
|
42515
|
+
createdAt: external_exports.iso.datetime(),
|
|
42516
|
+
status: external_exports.enum(["planned", "unknown"])
|
|
42517
|
+
});
|
|
42518
|
+
var walletEntrySchema = external_exports.object({
|
|
42519
|
+
createdAt: external_exports.iso.datetime(),
|
|
42520
|
+
label: external_exports.string().trim().min(1).max(80).optional(),
|
|
42521
|
+
spendCaps: spendCapsSchema,
|
|
42522
|
+
// On disk, null explicitly disables ceiling sweeps; an absent field is the
|
|
42523
|
+
// backwards-compatible 5 USDC default and is not written during reads.
|
|
42524
|
+
ceilingAtomic: external_exports.string().regex(/^\d+$/).nullable().optional(),
|
|
42525
|
+
ceilingSweepAttemptedAt: external_exports.iso.datetime().optional(),
|
|
42526
|
+
ceilingSweepPending: ceilingSweepPendingSchema.optional(),
|
|
42527
|
+
routerRefill: routerRefillSchema.optional(),
|
|
42528
|
+
link: agentLinkSchema.optional()
|
|
42529
|
+
});
|
|
42530
|
+
var walletRegistrySchema = external_exports.object({
|
|
42531
|
+
version: external_exports.literal(1),
|
|
42532
|
+
default: walletNameSchema.optional(),
|
|
42533
|
+
wallets: external_exports.record(walletNameSchema, walletEntrySchema)
|
|
42534
|
+
});
|
|
42535
|
+
async function spendCapsForWallet(store, name = DEFAULT_WALLET_NAME) {
|
|
42536
|
+
const entry = store.entry(name);
|
|
42537
|
+
if (entry) return { ...entry.spendCaps };
|
|
42538
|
+
return await readConfiguredSpendCaps(getVapiPaths(store.home).config);
|
|
42539
|
+
}
|
|
42540
|
+
async function readConfiguredSpendCaps(configPath) {
|
|
42541
|
+
try {
|
|
42542
|
+
const parsed = JSON.parse(await readFile8(configPath, "utf8"));
|
|
42543
|
+
const caps = typeof parsed === "object" && parsed !== null ? Reflect.get(parsed, "spendCaps") : void 0;
|
|
42544
|
+
const result = spendCapsSchema.safeParse(caps);
|
|
42545
|
+
if (result.success) return result.data;
|
|
42546
|
+
} catch {
|
|
42547
|
+
}
|
|
42548
|
+
return { ...DEFAULT_SPEND_CAPS };
|
|
42549
|
+
}
|
|
42550
|
+
|
|
41621
42551
|
// ../../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
42552
|
var AccountState = /* @__PURE__ */ ((AccountState2) => {
|
|
41623
42553
|
AccountState2[AccountState2["Uninitialized"] = 0] = "Uninitialized";
|
|
@@ -42627,6 +43557,104 @@ var ExactSvmScheme = class {
|
|
|
42627
43557
|
var addressEncoder = getAddressEncoder();
|
|
42628
43558
|
var compiledMessageDecoder = getCompiledTransactionMessageDecoder();
|
|
42629
43559
|
|
|
43560
|
+
// src/svm.ts
|
|
43561
|
+
var MAX_RPC_RESPONSE_BYTES = 4 * 1024 * 1024;
|
|
43562
|
+
function isSolanaAddress(value) {
|
|
43563
|
+
try {
|
|
43564
|
+
address(value);
|
|
43565
|
+
return true;
|
|
43566
|
+
} catch {
|
|
43567
|
+
return false;
|
|
43568
|
+
}
|
|
43569
|
+
}
|
|
43570
|
+
async function readSolanaUsdcBalance(args) {
|
|
43571
|
+
const accounts = await readSolanaUsdcAccounts(args);
|
|
43572
|
+
return accounts.reduce((total, account) => total + account.amountAtomic, 0n);
|
|
43573
|
+
}
|
|
43574
|
+
async function readSolanaUsdcAccounts(args) {
|
|
43575
|
+
if (!isSolanaAddress(args.address)) {
|
|
43576
|
+
throw new Error(`Invalid Solana address: ${args.address}.`);
|
|
43577
|
+
}
|
|
43578
|
+
const rpcUrl = requireRpcUrl(args.network, args.configured);
|
|
43579
|
+
const value = await solanaRpcCall(
|
|
43580
|
+
rpcUrl,
|
|
43581
|
+
"getTokenAccountsByOwner",
|
|
43582
|
+
[
|
|
43583
|
+
args.address,
|
|
43584
|
+
{ mint: args.configured.usdc },
|
|
43585
|
+
{ encoding: "jsonParsed", commitment: "confirmed" }
|
|
43586
|
+
],
|
|
43587
|
+
rpcOptionsFrom(args)
|
|
43588
|
+
);
|
|
43589
|
+
return value.map((entry) => {
|
|
43590
|
+
const info = entry.account.data.parsed.info;
|
|
43591
|
+
if (!isSolanaAddress(entry.pubkey) || info.mint !== args.configured.usdc || info.owner !== args.address || info.tokenAmount.decimals !== USDC_DECIMALS || !/^\d+$/.test(info.tokenAmount.amount)) {
|
|
43592
|
+
throw new Error("Solana RPC returned a malformed USDC token account.");
|
|
43593
|
+
}
|
|
43594
|
+
return { address: entry.pubkey, amountAtomic: BigInt(info.tokenAmount.amount) };
|
|
43595
|
+
});
|
|
43596
|
+
}
|
|
43597
|
+
async function solanaRpcCall(rpcUrl, method, params, options) {
|
|
43598
|
+
const fetchImpl = options.fetchImpl ?? createPublicFetch({
|
|
43599
|
+
allowPrivateNetwork: options.allowPrivateNetwork ?? false,
|
|
43600
|
+
...options.lookup ? { lookup: options.lookup } : {}
|
|
43601
|
+
});
|
|
43602
|
+
const response = await fetchImpl(rpcUrl, {
|
|
43603
|
+
method: "POST",
|
|
43604
|
+
headers: { "content-type": "application/json" },
|
|
43605
|
+
body: JSON.stringify({ jsonrpc: "2.0", id: 1, method, params }),
|
|
43606
|
+
redirect: "manual"
|
|
43607
|
+
});
|
|
43608
|
+
if (!response.ok) {
|
|
43609
|
+
discardBody(response);
|
|
43610
|
+
const rateLimit = response.status === 429 ? " (public RPC rate limit)" : "";
|
|
43611
|
+
throw new Error(`Solana RPC ${method} failed with HTTP ${response.status}${rateLimit}.`);
|
|
43612
|
+
}
|
|
43613
|
+
const envelope = await readBoundedRpcJson(response);
|
|
43614
|
+
if (!isRecord2(envelope) || envelope.jsonrpc !== "2.0") {
|
|
43615
|
+
throw new Error(`Solana RPC ${method} returned an invalid JSON-RPC envelope.`);
|
|
43616
|
+
}
|
|
43617
|
+
if (envelope.error !== void 0) {
|
|
43618
|
+
throw new Error(`Solana RPC ${method} failed: ${JSON.stringify(envelope.error)}.`);
|
|
43619
|
+
}
|
|
43620
|
+
const result = envelope.result;
|
|
43621
|
+
if (isRecord2(result) && "value" in result) return result.value;
|
|
43622
|
+
return result;
|
|
43623
|
+
}
|
|
43624
|
+
async function readBoundedRpcJson(response) {
|
|
43625
|
+
if (!response.body) throw new Error("Solana RPC response body is empty.");
|
|
43626
|
+
const reader = response.body.getReader();
|
|
43627
|
+
const chunks = [];
|
|
43628
|
+
let length = 0;
|
|
43629
|
+
while (true) {
|
|
43630
|
+
const next = await reader.read();
|
|
43631
|
+
if (next.done) break;
|
|
43632
|
+
length += next.value.byteLength;
|
|
43633
|
+
if (length > MAX_RPC_RESPONSE_BYTES) {
|
|
43634
|
+
await reader.cancel();
|
|
43635
|
+
throw new Error(`Solana RPC response exceeds ${MAX_RPC_RESPONSE_BYTES} bytes.`);
|
|
43636
|
+
}
|
|
43637
|
+
chunks.push(next.value);
|
|
43638
|
+
}
|
|
43639
|
+
const bytes = new Uint8Array(length);
|
|
43640
|
+
let offset = 0;
|
|
43641
|
+
for (const chunk of chunks) {
|
|
43642
|
+
bytes.set(chunk, offset);
|
|
43643
|
+
offset += chunk.byteLength;
|
|
43644
|
+
}
|
|
43645
|
+
return JSON.parse(new TextDecoder().decode(bytes));
|
|
43646
|
+
}
|
|
43647
|
+
function rpcOptionsFrom(options) {
|
|
43648
|
+
return {
|
|
43649
|
+
allowPrivateNetwork: options.allowPrivateNetwork,
|
|
43650
|
+
...options.fetchImpl ? { fetchImpl: options.fetchImpl } : {},
|
|
43651
|
+
...options.lookup ? { lookup: options.lookup } : {}
|
|
43652
|
+
};
|
|
43653
|
+
}
|
|
43654
|
+
function isRecord2(value) {
|
|
43655
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
43656
|
+
}
|
|
43657
|
+
|
|
42630
43658
|
// src/x402.ts
|
|
42631
43659
|
var MAX_CHALLENGE_BYTES = 1048576;
|
|
42632
43660
|
var MAX_UINT256 = (1n << 256n) - 1n;
|
|
@@ -42659,15 +43687,15 @@ var X402Error = class extends Error {
|
|
|
42659
43687
|
function isValidPaymentId(value) {
|
|
42660
43688
|
return typeof value === "string" && PAYMENT_ID_PATTERN.test(value);
|
|
42661
43689
|
}
|
|
42662
|
-
function createPaymentId(
|
|
43690
|
+
function createPaymentId(randomBytes9 = fillRandomBytes) {
|
|
42663
43691
|
const bytes = new Uint8Array(16);
|
|
42664
|
-
|
|
43692
|
+
randomBytes9(bytes);
|
|
42665
43693
|
return `pay_${bytesToHex(bytes).slice(2)}`;
|
|
42666
43694
|
}
|
|
42667
43695
|
function buildPaymentExtensions(serverExtensions, options = {}) {
|
|
42668
43696
|
const declaredBuilderCode = serverExtensions?.["builder-code"];
|
|
42669
|
-
const builderInfo =
|
|
42670
|
-
const serverBuilderInfo =
|
|
43697
|
+
const builderInfo = isRecord3(declaredBuilderCode) ? declaredBuilderCode.info : void 0;
|
|
43698
|
+
const serverBuilderInfo = isRecord3(builderInfo) ? builderInfo : void 0;
|
|
42671
43699
|
const declaredServerCodes = serverBuilderInfo?.s;
|
|
42672
43700
|
const serverCodes = typeof declaredServerCodes === "string" ? [declaredServerCodes] : Array.isArray(declaredServerCodes) ? declaredServerCodes : void 0;
|
|
42673
43701
|
const validServerCodes = [];
|
|
@@ -42684,10 +43712,10 @@ function buildPaymentExtensions(serverExtensions, options = {}) {
|
|
|
42684
43712
|
...typeof appCode === "string" && BUILDER_CODE_PATTERN.test(appCode) ? { a: appCode } : {},
|
|
42685
43713
|
s: [VAPI_BUILDER_CODE, ...validServerCodes]
|
|
42686
43714
|
},
|
|
42687
|
-
...
|
|
43715
|
+
...isRecord3(declaredBuilderCode) && declaredBuilderCode.schema !== void 0 ? { schema: declaredBuilderCode.schema } : {}
|
|
42688
43716
|
};
|
|
42689
43717
|
const paymentIdentifier = serverExtensions?.["payment-identifier"];
|
|
42690
|
-
if (!
|
|
43718
|
+
if (!isRecord3(paymentIdentifier)) {
|
|
42691
43719
|
return { ...serverExtensions, "builder-code": builderCode };
|
|
42692
43720
|
}
|
|
42693
43721
|
if (!isValidPaymentId(options.paymentId)) {
|
|
@@ -42696,7 +43724,7 @@ function buildPaymentExtensions(serverExtensions, options = {}) {
|
|
|
42696
43724
|
"paymentId must be 16-128 characters containing only letters, digits, underscores, or hyphens."
|
|
42697
43725
|
);
|
|
42698
43726
|
}
|
|
42699
|
-
const paymentInfo =
|
|
43727
|
+
const paymentInfo = isRecord3(paymentIdentifier.info) ? paymentIdentifier.info : {};
|
|
42700
43728
|
return {
|
|
42701
43729
|
...serverExtensions,
|
|
42702
43730
|
"builder-code": builderCode,
|
|
@@ -42707,7 +43735,7 @@ function buildPaymentExtensions(serverExtensions, options = {}) {
|
|
|
42707
43735
|
};
|
|
42708
43736
|
}
|
|
42709
43737
|
function parse402Challenge(challenge2, configuredNetworks, fallbackResourceUrl = "", requiredNetwork, expectedPayTo) {
|
|
42710
|
-
if (!
|
|
43738
|
+
if (!isRecord3(challenge2) || !Array.isArray(challenge2.accepts)) {
|
|
42711
43739
|
throw new X402Error("invalid_challenge", "x402 challenge has no accepts array.");
|
|
42712
43740
|
}
|
|
42713
43741
|
if (challenge2.x402Version !== void 0 && challenge2.x402Version !== 2) {
|
|
@@ -42716,7 +43744,7 @@ function parse402Challenge(challenge2, configuredNetworks, fallbackResourceUrl =
|
|
|
42716
43744
|
`Unsupported x402 version: ${String(challenge2.x402Version)}.`
|
|
42717
43745
|
);
|
|
42718
43746
|
}
|
|
42719
|
-
const candidates = challenge2.accepts.filter(
|
|
43747
|
+
const candidates = challenge2.accepts.filter(isRecord3);
|
|
42720
43748
|
const accepted = candidates.find((candidate) => {
|
|
42721
43749
|
const network2 = readString(candidate, "network");
|
|
42722
43750
|
const scheme = readString(candidate, "scheme");
|
|
@@ -42905,17 +43933,24 @@ function buildEip3009TypedData(args) {
|
|
|
42905
43933
|
if (!Number.isSafeInteger(args.nowSeconds) || args.nowSeconds < 0) {
|
|
42906
43934
|
throw new Error("nowSeconds must be a non-negative integer.");
|
|
42907
43935
|
}
|
|
43936
|
+
if (args.maximumValidBeforeSeconds !== void 0 && (!Number.isSafeInteger(args.maximumValidBeforeSeconds) || args.maximumValidBeforeSeconds <= args.nowSeconds)) {
|
|
43937
|
+
throw new Error("The payment allowance has expired; no authorization was signed.");
|
|
43938
|
+
}
|
|
42908
43939
|
if (!isEvmPaymentRequirements(args.quote.accepted)) {
|
|
42909
43940
|
throw new Error("EIP-3009 typed data can only be built for an EVM x402 quote.");
|
|
42910
43941
|
}
|
|
42911
43942
|
const accepted = args.quote.accepted;
|
|
42912
43943
|
const windowSeconds = Math.min(MAX_AUTHORIZATION_WINDOW_SECONDS, accepted.maxTimeoutSeconds);
|
|
43944
|
+
const validBefore = Math.min(
|
|
43945
|
+
args.nowSeconds + windowSeconds,
|
|
43946
|
+
args.maximumValidBeforeSeconds ?? Number.MAX_SAFE_INTEGER
|
|
43947
|
+
);
|
|
42913
43948
|
const authorization = {
|
|
42914
43949
|
from: getAddress(args.from),
|
|
42915
43950
|
to: accepted.payTo,
|
|
42916
43951
|
value: args.quote.amountAtomic.toString(),
|
|
42917
43952
|
validAfter: "0",
|
|
42918
|
-
validBefore:
|
|
43953
|
+
validBefore: validBefore.toString(),
|
|
42919
43954
|
nonce: args.nonce
|
|
42920
43955
|
};
|
|
42921
43956
|
return {
|
|
@@ -42941,7 +43976,7 @@ function buildEip3009TypedData(args) {
|
|
|
42941
43976
|
};
|
|
42942
43977
|
}
|
|
42943
43978
|
async function buildX402Payment(args) {
|
|
42944
|
-
const advertisesPaymentIdentifier =
|
|
43979
|
+
const advertisesPaymentIdentifier = isRecord3(args.quote.extensions?.["payment-identifier"]);
|
|
42945
43980
|
const paymentId = advertisesPaymentIdentifier ? args.paymentId ?? createPaymentId() : void 0;
|
|
42946
43981
|
const extensions = buildPaymentExtensions(args.quote.extensions, { paymentId });
|
|
42947
43982
|
if (isSvmPaymentRequirements(args.quote.accepted)) {
|
|
@@ -42986,7 +44021,8 @@ async function buildX402Payment(args) {
|
|
|
42986
44021
|
from: args.signer.address,
|
|
42987
44022
|
quote: args.quote,
|
|
42988
44023
|
nonce,
|
|
42989
|
-
nowSeconds: args.nowSeconds ?? Math.floor(Date.now() / 1e3)
|
|
44024
|
+
nowSeconds: args.nowSeconds ?? Math.floor(Date.now() / 1e3),
|
|
44025
|
+
...args.maximumValidBeforeSeconds === void 0 ? {} : { maximumValidBeforeSeconds: args.maximumValidBeforeSeconds }
|
|
42990
44026
|
});
|
|
42991
44027
|
const signature = await args.signer.signTypedData(typedData);
|
|
42992
44028
|
const payload = {
|
|
@@ -43009,6 +44045,7 @@ async function buildCompatibleX402Payment(args) {
|
|
|
43009
44045
|
signer: args.account,
|
|
43010
44046
|
quote: args.quote,
|
|
43011
44047
|
...args.nowSeconds === void 0 ? {} : { nowSeconds: args.nowSeconds },
|
|
44048
|
+
...args.maximumValidBeforeSeconds === void 0 ? {} : { maximumValidBeforeSeconds: args.maximumValidBeforeSeconds },
|
|
43012
44049
|
...args.nonce === void 0 ? {} : { nonce: args.nonce },
|
|
43013
44050
|
...args.fetchImpl === void 0 ? {} : { fetchImpl: args.fetchImpl },
|
|
43014
44051
|
...args.paymentId === void 0 ? {} : { paymentId: args.paymentId }
|
|
@@ -43068,7 +44105,7 @@ function parseSettlementResponse(headers) {
|
|
|
43068
44105
|
}
|
|
43069
44106
|
}
|
|
43070
44107
|
function classifySettlement(value) {
|
|
43071
|
-
if (!
|
|
44108
|
+
if (!isRecord3(value) || typeof value.success !== "boolean") {
|
|
43072
44109
|
return "unknown";
|
|
43073
44110
|
}
|
|
43074
44111
|
return value.success ? "succeeded" : "rejected";
|
|
@@ -43164,12 +44201,12 @@ function canonicalTokenDomain(network, configured) {
|
|
|
43164
44201
|
return configuredDomain;
|
|
43165
44202
|
}
|
|
43166
44203
|
function readBoundedJsonRecord(value) {
|
|
43167
|
-
if (!
|
|
44204
|
+
if (!isRecord3(value)) return null;
|
|
43168
44205
|
const seen = /* @__PURE__ */ new WeakSet();
|
|
43169
44206
|
const budget = { entries: 0 };
|
|
43170
44207
|
try {
|
|
43171
44208
|
const cloned = cloneBoundedJson(value, 0, seen, budget);
|
|
43172
|
-
return
|
|
44209
|
+
return isRecord3(cloned) ? cloned : null;
|
|
43173
44210
|
} catch {
|
|
43174
44211
|
return null;
|
|
43175
44212
|
}
|
|
@@ -43178,7 +44215,7 @@ function readChallengeExtensions(value) {
|
|
|
43178
44215
|
if (value === void 0) return void 0;
|
|
43179
44216
|
const extensions = readBoundedJsonRecord(value);
|
|
43180
44217
|
if (extensions) return extensions;
|
|
43181
|
-
if (!
|
|
44218
|
+
if (!isRecord3(value)) return void 0;
|
|
43182
44219
|
const paymentExtensions = /* @__PURE__ */ Object.create(null);
|
|
43183
44220
|
for (const name of ["builder-code", "payment-identifier"]) {
|
|
43184
44221
|
const extension = readBoundedJsonRecord(value[name]);
|
|
@@ -43231,7 +44268,7 @@ function cloneBoundedJson(value, depth, seen, budget) {
|
|
|
43231
44268
|
}
|
|
43232
44269
|
function readResource(value, fallbackUrl) {
|
|
43233
44270
|
if (typeof value === "string") return { url: value };
|
|
43234
|
-
if (!
|
|
44271
|
+
if (!isRecord3(value)) return { url: fallbackUrl };
|
|
43235
44272
|
const url2 = readString(value, "url") ?? fallbackUrl;
|
|
43236
44273
|
return {
|
|
43237
44274
|
url: url2,
|
|
@@ -43246,7 +44283,7 @@ function readString(value, key) {
|
|
|
43246
44283
|
const field = value[key];
|
|
43247
44284
|
return typeof field === "string" && field.length > 0 ? field : null;
|
|
43248
44285
|
}
|
|
43249
|
-
function
|
|
44286
|
+
function isRecord3(value) {
|
|
43250
44287
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
43251
44288
|
}
|
|
43252
44289
|
function decodeBase64Json(value) {
|
|
@@ -43262,421 +44299,6 @@ function encodeBase64Json(value) {
|
|
|
43262
44299
|
return globalThis.btoa(binary);
|
|
43263
44300
|
}
|
|
43264
44301
|
|
|
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
44302
|
// src/transfer.ts
|
|
43681
44303
|
var TransferError = class extends Error {
|
|
43682
44304
|
constructor(code, message, options = {}) {
|
|
@@ -43731,6 +44353,9 @@ async function transferBetweenAccounts(args) {
|
|
|
43731
44353
|
throw transferError("not_linked");
|
|
43732
44354
|
}
|
|
43733
44355
|
const fromAddress = getAddress(rawFromAddress);
|
|
44356
|
+
if (args.expectedFromAddress !== void 0 && (!isAddress(args.expectedFromAddress, { strict: false }) || !sameAddress(fromAddress, args.expectedFromAddress))) {
|
|
44357
|
+
throw transferError("account_address_mismatch");
|
|
44358
|
+
}
|
|
43734
44359
|
let siblingResult;
|
|
43735
44360
|
try {
|
|
43736
44361
|
siblingResult = await fetchSiblings({
|
|
@@ -43762,8 +44387,12 @@ async function transferBetweenAccounts(args) {
|
|
|
43762
44387
|
owner: siblingResult.owner,
|
|
43763
44388
|
siblings: siblingResult.siblings
|
|
43764
44389
|
});
|
|
44390
|
+
if (args.expectedToAddress !== void 0 && (!isAddress(args.expectedToAddress, { strict: false }) || !sameAddress(recipient.address, args.expectedToAddress))) {
|
|
44391
|
+
throw transferError("account_address_mismatch");
|
|
44392
|
+
}
|
|
43765
44393
|
const purpose = args.purpose ?? "send";
|
|
43766
|
-
|
|
44394
|
+
const isSweepTarget = recipient.toKind === "owner" || recipient.toKind === "account" && args.sweepParent !== void 0 && isAddress(args.sweepParent, { strict: false }) && getAddress(args.sweepParent) === getAddress(recipient.address);
|
|
44395
|
+
if (purpose === "sweep" && !isSweepTarget) {
|
|
43767
44396
|
throw transferError("sweep_not_owner");
|
|
43768
44397
|
}
|
|
43769
44398
|
const home = args.home ?? args.store.home;
|
|
@@ -43784,7 +44413,7 @@ async function transferBetweenAccounts(args) {
|
|
|
43784
44413
|
if (args.resume !== void 0) {
|
|
43785
44414
|
return await resumeTransfer(context, args.resume);
|
|
43786
44415
|
}
|
|
43787
|
-
const nonce = args.nonce ?? bytesToHex(
|
|
44416
|
+
const nonce = args.nonce ?? bytesToHex(randomBytes5(32));
|
|
43788
44417
|
if (!NONCE_PATTERN.test(nonce)) {
|
|
43789
44418
|
throw new TransferError(
|
|
43790
44419
|
"authorization_invalid",
|
|
@@ -44009,8 +44638,8 @@ async function postRelay(context, request, options) {
|
|
|
44009
44638
|
`wallet:${context.args.from}`,
|
|
44010
44639
|
async () => {
|
|
44011
44640
|
await context.args.store.reload();
|
|
44012
|
-
const
|
|
44013
|
-
if (
|
|
44641
|
+
const link2 = context.args.store.entry(context.args.from)?.link;
|
|
44642
|
+
if (link2 === void 0 || safeOrigin(link2.apiBase) !== safeOrigin(context.args.apiBase)) {
|
|
44014
44643
|
throw transferError("not_linked");
|
|
44015
44644
|
}
|
|
44016
44645
|
return await agentFetch(
|
|
@@ -44167,7 +44796,7 @@ async function findTransferReceipt(context, nonce) {
|
|
|
44167
44796
|
return [...receipts].reverse().find((candidate) => candidate.transfer?.nonce.toLowerCase() === nonce.toLowerCase());
|
|
44168
44797
|
}
|
|
44169
44798
|
async function withTransferLock(context, nonce, operation) {
|
|
44170
|
-
await
|
|
44799
|
+
await mkdir9(dirname6(context.receiptsPath), { recursive: true, mode: 448 });
|
|
44171
44800
|
const lockPath = `${context.receiptsPath}.${context.args.from}.${nonce.toLowerCase()}.transfer.lock`;
|
|
44172
44801
|
const requestTimeout = Math.max(0, context.args.timeoutMs ?? DEFAULT_TIMEOUT_MS);
|
|
44173
44802
|
const deadline = Date.now() + Math.max(5e3, requestTimeout + 5e3);
|
|
@@ -44175,7 +44804,7 @@ async function withTransferLock(context, nonce, operation) {
|
|
|
44175
44804
|
let handle;
|
|
44176
44805
|
while (!handle) {
|
|
44177
44806
|
try {
|
|
44178
|
-
handle = await
|
|
44807
|
+
handle = await open8(lockPath, "wx", 384);
|
|
44179
44808
|
} catch (error61) {
|
|
44180
44809
|
if (!(error61 instanceof Error && "code" in error61 && error61.code === "EEXIST")) throw error61;
|
|
44181
44810
|
await removeStaleTransferLock(lockPath, staleAfterMs);
|
|
@@ -44189,13 +44818,13 @@ async function withTransferLock(context, nonce, operation) {
|
|
|
44189
44818
|
return await operation();
|
|
44190
44819
|
} finally {
|
|
44191
44820
|
await handle.close();
|
|
44192
|
-
await
|
|
44821
|
+
await unlink6(lockPath).catch(() => void 0);
|
|
44193
44822
|
}
|
|
44194
44823
|
}
|
|
44195
44824
|
async function removeStaleTransferLock(path, staleAfterMs) {
|
|
44196
44825
|
try {
|
|
44197
|
-
const metadata = await
|
|
44198
|
-
if (Date.now() - metadata.mtimeMs > staleAfterMs) await
|
|
44826
|
+
const metadata = await stat7(path);
|
|
44827
|
+
if (Date.now() - metadata.mtimeMs > staleAfterMs) await unlink6(path);
|
|
44199
44828
|
} catch (error61) {
|
|
44200
44829
|
if (!(error61 instanceof Error && "code" in error61 && error61.code === "ENOENT")) throw error61;
|
|
44201
44830
|
}
|
|
@@ -44349,6 +44978,7 @@ function isTransferErrorCode(value) {
|
|
|
44349
44978
|
"resume_not_found",
|
|
44350
44979
|
"resume_mismatch",
|
|
44351
44980
|
"signer_mismatch",
|
|
44981
|
+
"account_address_mismatch",
|
|
44352
44982
|
"signing_failed"
|
|
44353
44983
|
].includes(value);
|
|
44354
44984
|
}
|
|
@@ -44360,7 +44990,7 @@ function transferError(code, options = {}) {
|
|
|
44360
44990
|
unsupported: "This vAPI server does not relay transfers yet. No money moved.",
|
|
44361
44991
|
recipient_not_allowed: "The recipient is not the owner or an active sibling account. No money moved.",
|
|
44362
44992
|
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.",
|
|
44993
|
+
sweep_not_owner: "A sweep can only send funds to the owner or its treasury. No money moved.",
|
|
44364
44994
|
per_call_cap_exceeded: "The amount exceeds the sending account's per-call cap. No money moved.",
|
|
44365
44995
|
per_day_cap_exceeded: "The amount exceeds the sending account's remaining daily cap. No money moved.",
|
|
44366
44996
|
payer_mismatch: "The relay rejected the sending account. No money moved.",
|
|
@@ -44373,17 +45003,644 @@ function transferError(code, options = {}) {
|
|
|
44373
45003
|
resume_not_found: "No resumable transfer was found for that nonce. No money moved.",
|
|
44374
45004
|
resume_mismatch: "The resumed transfer does not match the original request. No money moved.",
|
|
44375
45005
|
signer_mismatch: "The unlocked signer does not match the sending account. No money moved.",
|
|
45006
|
+
account_address_mismatch: "The sending or recipient account no longer matches the address recorded by its movement. No money moved.",
|
|
44376
45007
|
signing_failed: "The transfer authorization could not be signed. No money moved."
|
|
44377
45008
|
};
|
|
44378
45009
|
return new TransferError(code, messages[code], options);
|
|
44379
45010
|
}
|
|
44380
45011
|
|
|
45012
|
+
// src/movement.ts
|
|
45013
|
+
var movementIdSchema = external_exports.string().regex(/^mv_[A-Za-z0-9_-]{8,128}$/);
|
|
45014
|
+
var nonceSchema = external_exports.string().regex(/^0x[0-9a-fA-F]{64}$/);
|
|
45015
|
+
var amountSchema = external_exports.string().regex(/^\d+\.\d{2}$/);
|
|
45016
|
+
var movementReasonSchema = external_exports.enum([
|
|
45017
|
+
"distribute",
|
|
45018
|
+
"allocate",
|
|
45019
|
+
"rebalance",
|
|
45020
|
+
"delegate",
|
|
45021
|
+
"sweep",
|
|
45022
|
+
"send"
|
|
45023
|
+
]);
|
|
45024
|
+
var movementPurposeSchema = external_exports.enum(["send", "sweep"]);
|
|
45025
|
+
var movementAddressSchema = external_exports.string().refine((value) => isAddress(value, { strict: false }));
|
|
45026
|
+
var movementLegSchema = external_exports.strictObject({
|
|
45027
|
+
from: walletNameSchema,
|
|
45028
|
+
to: external_exports.string().trim().min(1),
|
|
45029
|
+
fromAddress: movementAddressSchema.optional(),
|
|
45030
|
+
toAddress: movementAddressSchema.optional(),
|
|
45031
|
+
amountUsd: amountSchema,
|
|
45032
|
+
purpose: movementPurposeSchema,
|
|
45033
|
+
nonce: nonceSchema,
|
|
45034
|
+
status: external_exports.enum(["planned", "sent", "failed", "unknown", "cancelled"]),
|
|
45035
|
+
txHash: external_exports.string().regex(/^0x[0-9a-fA-F]+$/).optional(),
|
|
45036
|
+
reason: external_exports.string().min(1).optional(),
|
|
45037
|
+
retryable: external_exports.boolean().optional(),
|
|
45038
|
+
restored: external_exports.literal(true).optional(),
|
|
45039
|
+
addressBindingSource: external_exports.enum(["journal", "terminal-review"]).optional()
|
|
45040
|
+
});
|
|
45041
|
+
var movementSchema = external_exports.strictObject({
|
|
45042
|
+
v: external_exports.literal(2),
|
|
45043
|
+
id: movementIdSchema,
|
|
45044
|
+
reason: movementReasonSchema,
|
|
45045
|
+
from: walletNameSchema,
|
|
45046
|
+
// The only account a `sweep` leg may name besides the owner. Sweeps skip the
|
|
45047
|
+
// spend caps, so the parent comes from the plan, never from a leg's `to`.
|
|
45048
|
+
treasury: walletNameSchema.optional(),
|
|
45049
|
+
network: external_exports.enum([BASE_MAINNET_CAIP2, ARC_MAINNET_CAIP2]),
|
|
45050
|
+
createdAt: external_exports.iso.datetime(),
|
|
45051
|
+
legs: external_exports.array(movementLegSchema).min(1)
|
|
45052
|
+
});
|
|
45053
|
+
var legacyMovementSchema = external_exports.strictObject({
|
|
45054
|
+
v: external_exports.literal(1),
|
|
45055
|
+
id: movementIdSchema,
|
|
45056
|
+
reason: external_exports.literal("distribute"),
|
|
45057
|
+
from: walletNameSchema,
|
|
45058
|
+
network: external_exports.enum([BASE_MAINNET_CAIP2, ARC_MAINNET_CAIP2]),
|
|
45059
|
+
createdAt: external_exports.iso.datetime(),
|
|
45060
|
+
legs: external_exports.array(
|
|
45061
|
+
external_exports.strictObject({
|
|
45062
|
+
to: external_exports.string().trim().min(1),
|
|
45063
|
+
amountUsd: amountSchema,
|
|
45064
|
+
nonce: nonceSchema,
|
|
45065
|
+
status: external_exports.enum(["planned", "sent", "failed", "unknown"]),
|
|
45066
|
+
txHash: external_exports.string().regex(/^0x[0-9a-fA-F]+$/).optional(),
|
|
45067
|
+
reason: external_exports.string().min(1).optional(),
|
|
45068
|
+
retryable: external_exports.boolean().optional()
|
|
45069
|
+
})
|
|
45070
|
+
).min(1)
|
|
45071
|
+
});
|
|
45072
|
+
var MovementError = class extends Error {
|
|
45073
|
+
constructor(code, message, movementId) {
|
|
45074
|
+
super(message);
|
|
45075
|
+
this.code = code;
|
|
45076
|
+
this.movementId = movementId;
|
|
45077
|
+
this.name = "MovementError";
|
|
45078
|
+
}
|
|
45079
|
+
code;
|
|
45080
|
+
movementId;
|
|
45081
|
+
};
|
|
45082
|
+
async function listUnfinishedMovements(options) {
|
|
45083
|
+
const directory = movementsDirectory(options.home);
|
|
45084
|
+
let names;
|
|
45085
|
+
try {
|
|
45086
|
+
names = await readdir3(directory);
|
|
45087
|
+
} catch (error61) {
|
|
45088
|
+
if (isMissingFile(error61)) return [];
|
|
45089
|
+
throw error61;
|
|
45090
|
+
}
|
|
45091
|
+
const summaries = [];
|
|
45092
|
+
for (const name of names.filter((candidate) => candidate.endsWith(".json")).sort()) {
|
|
45093
|
+
const movement = parseMovement(JSON.parse(await readFile9(join5(directory, name), "utf8")));
|
|
45094
|
+
if (options.from !== void 0 && !movement.legs.some((leg) => leg.from === options.from)) {
|
|
45095
|
+
continue;
|
|
45096
|
+
}
|
|
45097
|
+
const pendingLegs = movement.legs.filter((leg) => leg.status === "planned").length;
|
|
45098
|
+
const unknownLegs = movement.legs.filter((leg) => leg.status === "unknown").length;
|
|
45099
|
+
if (!await movementHasOpenLeg(options.home, movement, options.from)) continue;
|
|
45100
|
+
summaries.push({
|
|
45101
|
+
id: movement.id,
|
|
45102
|
+
from: movement.from,
|
|
45103
|
+
network: movement.network,
|
|
45104
|
+
createdAt: movement.createdAt,
|
|
45105
|
+
pendingLegs,
|
|
45106
|
+
unknownLegs
|
|
45107
|
+
});
|
|
45108
|
+
}
|
|
45109
|
+
return summaries.sort(
|
|
45110
|
+
(left, right) => left.createdAt.localeCompare(right.createdAt) || left.id.localeCompare(right.id)
|
|
45111
|
+
);
|
|
45112
|
+
}
|
|
45113
|
+
async function movementHasOpenLeg(home, movement, from15) {
|
|
45114
|
+
for (const leg of movement.legs) {
|
|
45115
|
+
if (from15 !== void 0 && leg.from !== from15) continue;
|
|
45116
|
+
if (isOpenMovementLeg(leg, "unsigned")) return true;
|
|
45117
|
+
if (leg.status !== "failed") continue;
|
|
45118
|
+
const journal = await readTransferJournal(home, leg.from, leg.nonce);
|
|
45119
|
+
if (isOpenMovementLeg(leg, journal.signed ? "signed" : "unsigned")) return true;
|
|
45120
|
+
}
|
|
45121
|
+
return false;
|
|
45122
|
+
}
|
|
45123
|
+
async function readMovement(home, id) {
|
|
45124
|
+
const safeId = parseMovementId(id);
|
|
45125
|
+
try {
|
|
45126
|
+
return parseMovement(
|
|
45127
|
+
JSON.parse(await readFile9(join5(movementsDirectory(home), `${safeId}.json`), "utf8"))
|
|
45128
|
+
);
|
|
45129
|
+
} catch (error61) {
|
|
45130
|
+
if (isMissingFile(error61)) {
|
|
45131
|
+
throw new MovementError(
|
|
45132
|
+
"movement_not_found",
|
|
45133
|
+
`No movement named ${safeId} was found.`,
|
|
45134
|
+
safeId
|
|
45135
|
+
);
|
|
45136
|
+
}
|
|
45137
|
+
throw error61;
|
|
45138
|
+
}
|
|
45139
|
+
}
|
|
45140
|
+
async function reconcileUnresolvedMovementsForAccountLocked(home, account, deps) {
|
|
45141
|
+
for (const summary of await listUnfinishedMovements({ home, from: account })) {
|
|
45142
|
+
let movement = await readMovement(home, summary.id);
|
|
45143
|
+
try {
|
|
45144
|
+
for (let index2 = 0; index2 < movement.legs.length; index2 += 1) {
|
|
45145
|
+
const leg = movement.legs[index2];
|
|
45146
|
+
if (leg?.from !== account || leg.status !== "planned" && !(leg.status === "failed" && leg.retryable === true)) {
|
|
45147
|
+
continue;
|
|
45148
|
+
}
|
|
45149
|
+
const reconciled = await reconcileJournaledLeg(movement, index2, home);
|
|
45150
|
+
if (reconciled !== movement) {
|
|
45151
|
+
movement = reconciled;
|
|
45152
|
+
await writeMovement(home, movement);
|
|
45153
|
+
}
|
|
45154
|
+
}
|
|
45155
|
+
for (let index2 = 0; index2 < movement.legs.length; index2 += 1) {
|
|
45156
|
+
const leg = movement.legs[index2];
|
|
45157
|
+
if (leg?.from !== account || leg.status !== "unknown") continue;
|
|
45158
|
+
movement = await resolveUnknownLeg(movement, index2, home, deps);
|
|
45159
|
+
}
|
|
45160
|
+
for (let index2 = 0; index2 < movement.legs.length; index2 += 1) {
|
|
45161
|
+
const leg = movement.legs[index2];
|
|
45162
|
+
if (leg?.from !== account || leg.status !== "failed") continue;
|
|
45163
|
+
movement = await prepareFailedLeg(movement, index2, home, deps);
|
|
45164
|
+
}
|
|
45165
|
+
} catch {
|
|
45166
|
+
}
|
|
45167
|
+
if (await movementHasOpenLeg(home, movement, account)) return movement;
|
|
45168
|
+
}
|
|
45169
|
+
return void 0;
|
|
45170
|
+
}
|
|
45171
|
+
async function resolveUnknownLeg(movement, index2, home, deps) {
|
|
45172
|
+
const leg = movement.legs[index2];
|
|
45173
|
+
if (isExpiredRestoredReview(leg)) {
|
|
45174
|
+
if (deps.replaceExpiredRestored !== true) {
|
|
45175
|
+
await throwRestoredLegReview(movement, leg, deps);
|
|
45176
|
+
}
|
|
45177
|
+
const journal2 = await readTransferJournal(home, leg.from, leg.nonce);
|
|
45178
|
+
await releaseExpiredReservation(leg, journal2, home, deps);
|
|
45179
|
+
const bound = leg.fromAddress === void 0 || leg.toAddress === void 0 ? await bindLegAddresses(leg, deps) : leg;
|
|
45180
|
+
movement = replaceLeg(
|
|
45181
|
+
movement,
|
|
45182
|
+
index2,
|
|
45183
|
+
plannedLeg(bound, (deps.randomNonce ?? createMovementNonce)())
|
|
45184
|
+
);
|
|
45185
|
+
await writeMovement(home, movement);
|
|
45186
|
+
return movement;
|
|
45187
|
+
}
|
|
45188
|
+
try {
|
|
45189
|
+
const result = await callTransfer(movement, leg, deps, true);
|
|
45190
|
+
if (result.status === "sent") {
|
|
45191
|
+
movement = replaceLeg(movement, index2, sentLeg(leg, result.txHash));
|
|
45192
|
+
await writeMovement(home, movement);
|
|
45193
|
+
return movement;
|
|
45194
|
+
}
|
|
45195
|
+
} catch (error61) {
|
|
45196
|
+
if (error61 instanceof TransferError || error61 instanceof SweepParentUnknownError) {
|
|
45197
|
+
} else {
|
|
45198
|
+
await reconcileCrash(movement, index2, home);
|
|
45199
|
+
throw error61;
|
|
45200
|
+
}
|
|
45201
|
+
}
|
|
45202
|
+
const journal = await readTransferJournal(home, leg.from, leg.nonce);
|
|
45203
|
+
const state = await authorizationState(movement, leg, journal.validBefore, home, deps);
|
|
45204
|
+
if (state === "settled") {
|
|
45205
|
+
movement = replaceLeg(movement, index2, sentLeg(leg));
|
|
45206
|
+
} else if (state === "expired") {
|
|
45207
|
+
if (leg.restored === true && deps.replaceExpiredRestored !== true) {
|
|
45208
|
+
movement = replaceLeg(movement, index2, restoredExpiredReviewLeg(leg));
|
|
45209
|
+
await writeMovement(home, movement);
|
|
45210
|
+
await throwRestoredLegReview(movement, movement.legs[index2], deps);
|
|
45211
|
+
}
|
|
45212
|
+
await releaseExpiredReservation(leg, journal, home, deps);
|
|
45213
|
+
movement = replaceLeg(
|
|
45214
|
+
movement,
|
|
45215
|
+
index2,
|
|
45216
|
+
plannedLeg(leg, (deps.randomNonce ?? createMovementNonce)())
|
|
45217
|
+
);
|
|
45218
|
+
}
|
|
45219
|
+
await writeMovement(home, movement);
|
|
45220
|
+
return movement;
|
|
45221
|
+
}
|
|
45222
|
+
async function reconcileJournaledLeg(movement, index2, home) {
|
|
45223
|
+
const leg = movement.legs[index2];
|
|
45224
|
+
const journal = await readTransferJournal(home, leg.from, leg.nonce);
|
|
45225
|
+
if (journal.latest?.transfer?.status === "sent") {
|
|
45226
|
+
return replaceLeg(
|
|
45227
|
+
movement,
|
|
45228
|
+
index2,
|
|
45229
|
+
sentLeg(
|
|
45230
|
+
leg,
|
|
45231
|
+
journal.latest.transfer.txHash === null ? void 0 : journal.latest.transfer.txHash
|
|
45232
|
+
)
|
|
45233
|
+
);
|
|
45234
|
+
}
|
|
45235
|
+
return journal.signed ? replaceLeg(movement, index2, unknownLeg(leg)) : movement;
|
|
45236
|
+
}
|
|
45237
|
+
async function prepareFailedLeg(movement, index2, home, deps) {
|
|
45238
|
+
const leg = movement.legs[index2];
|
|
45239
|
+
if (leg.retryable === true) {
|
|
45240
|
+
const reconciled = await reconcileJournaledLeg(movement, index2, home);
|
|
45241
|
+
if (reconciled !== movement) {
|
|
45242
|
+
await writeMovement(home, reconciled);
|
|
45243
|
+
return reconciled;
|
|
45244
|
+
}
|
|
45245
|
+
return replaceLeg(movement, index2, plannedLeg(leg, leg.nonce, true));
|
|
45246
|
+
}
|
|
45247
|
+
const journal = await readTransferJournal(home, leg.from, leg.nonce);
|
|
45248
|
+
if (!journal.signed) return movement;
|
|
45249
|
+
const state = await authorizationState(movement, leg, journal.validBefore, home, deps);
|
|
45250
|
+
if (state === "settled") {
|
|
45251
|
+
movement = replaceLeg(movement, index2, sentLeg(leg));
|
|
45252
|
+
} else if (state === "expired") {
|
|
45253
|
+
if (leg.restored === true && deps.replaceExpiredRestored !== true) {
|
|
45254
|
+
movement = replaceLeg(movement, index2, restoredExpiredReviewLeg(leg));
|
|
45255
|
+
await writeMovement(home, movement);
|
|
45256
|
+
await throwRestoredLegReview(movement, movement.legs[index2], deps);
|
|
45257
|
+
}
|
|
45258
|
+
await releaseExpiredReservation(leg, journal, home, deps);
|
|
45259
|
+
movement = replaceLeg(
|
|
45260
|
+
movement,
|
|
45261
|
+
index2,
|
|
45262
|
+
plannedLeg(leg, (deps.randomNonce ?? createMovementNonce)())
|
|
45263
|
+
);
|
|
45264
|
+
}
|
|
45265
|
+
await writeMovement(home, movement);
|
|
45266
|
+
return movement;
|
|
45267
|
+
}
|
|
45268
|
+
async function callTransfer(movement, leg, deps, resume) {
|
|
45269
|
+
if (leg.fromAddress !== void 0 && leg.toAddress !== void 0) {
|
|
45270
|
+
await assertLegAddressBinding(movement, leg, deps);
|
|
45271
|
+
} else if (!resume) {
|
|
45272
|
+
throw movementAddressUnboundError(movement);
|
|
45273
|
+
}
|
|
45274
|
+
const transfer = deps.transfer ?? transferBetweenAccounts;
|
|
45275
|
+
const sweepParent = await readSweepParent(movement, leg, deps);
|
|
45276
|
+
return await transfer({
|
|
45277
|
+
store: deps.store,
|
|
45278
|
+
secrets: deps.secrets,
|
|
45279
|
+
apiBase: deps.apiBase,
|
|
45280
|
+
from: leg.from,
|
|
45281
|
+
to: leg.to,
|
|
45282
|
+
amountUsd: leg.amountUsd,
|
|
45283
|
+
network: movement.network,
|
|
45284
|
+
purpose: leg.purpose,
|
|
45285
|
+
expectedFromAddress: leg.fromAddress,
|
|
45286
|
+
expectedToAddress: leg.toAddress,
|
|
45287
|
+
...sweepParent === void 0 ? {} : { sweepParent },
|
|
45288
|
+
home: deps.home ?? deps.store.home,
|
|
45289
|
+
...resume ? { resume: leg.nonce } : { nonce: leg.nonce },
|
|
45290
|
+
...deps.unlock === void 0 ? {} : { unlock: deps.unlock },
|
|
45291
|
+
...deps.fetchImpl === void 0 ? {} : { fetchImpl: deps.fetchImpl },
|
|
45292
|
+
...deps.now === void 0 ? {} : { now: deps.now },
|
|
45293
|
+
...deps.timeoutMs === void 0 ? {} : { timeoutMs: deps.timeoutMs },
|
|
45294
|
+
...deps.ledgerPath === void 0 ? {} : { ledgerPath: deps.ledgerPath }
|
|
45295
|
+
});
|
|
45296
|
+
}
|
|
45297
|
+
async function reconcileCrash(movement, index2, home) {
|
|
45298
|
+
const leg = movement.legs[index2];
|
|
45299
|
+
const journal = await readTransferJournal(home, leg.from, leg.nonce);
|
|
45300
|
+
let reconciled = leg;
|
|
45301
|
+
if (journal.latest?.transfer?.status === "sent") {
|
|
45302
|
+
reconciled = sentLeg(
|
|
45303
|
+
leg,
|
|
45304
|
+
journal.latest.transfer.txHash === null ? void 0 : journal.latest.transfer.txHash
|
|
45305
|
+
);
|
|
45306
|
+
} else if (journal.latest?.transfer?.status === "failed") {
|
|
45307
|
+
reconciled = journalFailedLeg(leg, journal.latest.error?.code ?? "transfer_failed");
|
|
45308
|
+
} else if (journal.signed) {
|
|
45309
|
+
reconciled = unknownLeg(leg);
|
|
45310
|
+
}
|
|
45311
|
+
await writeMovement(home, replaceLeg(movement, index2, reconciled));
|
|
45312
|
+
}
|
|
45313
|
+
async function authorizationState(movement, leg, validBefore, home, deps) {
|
|
45314
|
+
if (deps.authorizationState !== void 0) {
|
|
45315
|
+
return await deps.authorizationState({
|
|
45316
|
+
movement,
|
|
45317
|
+
leg,
|
|
45318
|
+
...validBefore === void 0 ? {} : { validBefore }
|
|
45319
|
+
});
|
|
45320
|
+
}
|
|
45321
|
+
const journal = await readTransferJournal(home, leg.from, leg.nonce);
|
|
45322
|
+
const receipt = journal.request;
|
|
45323
|
+
const request = receipt?.transfer?.request;
|
|
45324
|
+
if (receipt === void 0 || request === void 0) {
|
|
45325
|
+
throw new MovementError(
|
|
45326
|
+
"invalid_movement",
|
|
45327
|
+
`Movement ${movement.id} has no journaled authorization for nonce ${leg.nonce}.`,
|
|
45328
|
+
movement.id
|
|
45329
|
+
);
|
|
45330
|
+
}
|
|
45331
|
+
const config2 = await loadConfig(getVapiPaths(home).config);
|
|
45332
|
+
const settlementReceipt = {
|
|
45333
|
+
...receipt,
|
|
45334
|
+
authorization: {
|
|
45335
|
+
from: request.authorization.from,
|
|
45336
|
+
nonce: request.authorization.nonce,
|
|
45337
|
+
validBefore: request.authorization.validBefore
|
|
45338
|
+
}
|
|
45339
|
+
};
|
|
45340
|
+
return (await checkReceiptSettlement(settlementReceipt, config2, {
|
|
45341
|
+
...deps.fetchImpl === void 0 ? {} : { fetchImpl: deps.fetchImpl }
|
|
45342
|
+
})).state;
|
|
45343
|
+
}
|
|
45344
|
+
async function releaseExpiredReservation(leg, journal, home, deps) {
|
|
45345
|
+
if (journal.reservedOn === void 0) return;
|
|
45346
|
+
await releaseSpend(parseUsdCents(leg.amountUsd) * 10000n, {
|
|
45347
|
+
reservedOn: journal.reservedOn,
|
|
45348
|
+
ledgerPath: deps.ledgerPath ?? getVapiPaths(home).ledger,
|
|
45349
|
+
now: new Date((deps.now ?? Date.now)()),
|
|
45350
|
+
wallet: leg.from,
|
|
45351
|
+
reservationId: leg.nonce.toLowerCase()
|
|
45352
|
+
});
|
|
45353
|
+
}
|
|
45354
|
+
function sentLeg(leg, txHash) {
|
|
45355
|
+
return {
|
|
45356
|
+
from: leg.from,
|
|
45357
|
+
to: leg.to,
|
|
45358
|
+
...legAddressBinding(leg),
|
|
45359
|
+
amountUsd: leg.amountUsd,
|
|
45360
|
+
purpose: leg.purpose,
|
|
45361
|
+
nonce: leg.nonce,
|
|
45362
|
+
status: "sent",
|
|
45363
|
+
...restoredMarker(leg),
|
|
45364
|
+
...txHash === void 0 || txHash === null ? {} : { txHash }
|
|
45365
|
+
};
|
|
45366
|
+
}
|
|
45367
|
+
function unknownLeg(leg) {
|
|
45368
|
+
return {
|
|
45369
|
+
from: leg.from,
|
|
45370
|
+
to: leg.to,
|
|
45371
|
+
...legAddressBinding(leg),
|
|
45372
|
+
amountUsd: leg.amountUsd,
|
|
45373
|
+
purpose: leg.purpose,
|
|
45374
|
+
nonce: leg.nonce,
|
|
45375
|
+
status: "unknown",
|
|
45376
|
+
...restoredMarker(leg)
|
|
45377
|
+
};
|
|
45378
|
+
}
|
|
45379
|
+
function journalFailedLeg(leg, reason) {
|
|
45380
|
+
return {
|
|
45381
|
+
from: leg.from,
|
|
45382
|
+
to: leg.to,
|
|
45383
|
+
...legAddressBinding(leg),
|
|
45384
|
+
amountUsd: leg.amountUsd,
|
|
45385
|
+
purpose: leg.purpose,
|
|
45386
|
+
nonce: leg.nonce,
|
|
45387
|
+
status: "failed",
|
|
45388
|
+
reason,
|
|
45389
|
+
...restoredMarker(leg)
|
|
45390
|
+
};
|
|
45391
|
+
}
|
|
45392
|
+
function plannedLeg(leg, nonce, preserveRestored = false) {
|
|
45393
|
+
return {
|
|
45394
|
+
from: leg.from,
|
|
45395
|
+
to: leg.to,
|
|
45396
|
+
...legAddressBinding(leg),
|
|
45397
|
+
amountUsd: leg.amountUsd,
|
|
45398
|
+
purpose: leg.purpose,
|
|
45399
|
+
nonce,
|
|
45400
|
+
status: "planned",
|
|
45401
|
+
...preserveRestored ? restoredMarker(leg) : {}
|
|
45402
|
+
};
|
|
45403
|
+
}
|
|
45404
|
+
function restoredExpiredReviewLeg(leg) {
|
|
45405
|
+
return {
|
|
45406
|
+
from: leg.from,
|
|
45407
|
+
to: leg.to,
|
|
45408
|
+
...legAddressBinding(leg),
|
|
45409
|
+
amountUsd: leg.amountUsd,
|
|
45410
|
+
purpose: leg.purpose,
|
|
45411
|
+
nonce: leg.nonce,
|
|
45412
|
+
status: "unknown",
|
|
45413
|
+
reason: "restored_nonce_expired",
|
|
45414
|
+
restored: true
|
|
45415
|
+
};
|
|
45416
|
+
}
|
|
45417
|
+
function restoredMarker(leg) {
|
|
45418
|
+
return leg.restored === true ? { restored: true } : {};
|
|
45419
|
+
}
|
|
45420
|
+
function legAddressBinding(leg) {
|
|
45421
|
+
return {
|
|
45422
|
+
...leg.fromAddress === void 0 ? {} : { fromAddress: leg.fromAddress },
|
|
45423
|
+
...leg.toAddress === void 0 ? {} : { toAddress: leg.toAddress },
|
|
45424
|
+
...leg.addressBindingSource === void 0 ? {} : { addressBindingSource: leg.addressBindingSource }
|
|
45425
|
+
};
|
|
45426
|
+
}
|
|
45427
|
+
function isExpiredRestoredReview(leg) {
|
|
45428
|
+
return leg.restored === true && leg.reason === "restored_nonce_expired";
|
|
45429
|
+
}
|
|
45430
|
+
async function throwRestoredLegReview(movement, leg, deps, kind = "expired") {
|
|
45431
|
+
const address2 = await (deps.addressReader === void 0 ? deps.store.readAddress(leg.from) : deps.addressReader(leg.from));
|
|
45432
|
+
const explorer = address2 === void 0 ? void 0 : explorerAddressUrl(movement.network, address2);
|
|
45433
|
+
const addressHint = address2 ?? `account ${leg.from}`;
|
|
45434
|
+
const explorerHint = explorer === void 0 ? "a block explorer" : explorer;
|
|
45435
|
+
throw new MovementError(
|
|
45436
|
+
"restored_leg_review",
|
|
45437
|
+
`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.`,
|
|
45438
|
+
movement.id
|
|
45439
|
+
);
|
|
45440
|
+
}
|
|
45441
|
+
function replaceLeg(movement, index2, leg) {
|
|
45442
|
+
const legs = [...movement.legs];
|
|
45443
|
+
legs[index2] = movementLegSchema.parse(leg);
|
|
45444
|
+
return movementSchema.parse({ ...movement, legs });
|
|
45445
|
+
}
|
|
45446
|
+
function parseMovement(value) {
|
|
45447
|
+
const current = movementSchema.safeParse(value);
|
|
45448
|
+
if (current.success) return current.data;
|
|
45449
|
+
const legacy = legacyMovementSchema.parse(value);
|
|
45450
|
+
return movementSchema.parse({
|
|
45451
|
+
...legacy,
|
|
45452
|
+
v: 2,
|
|
45453
|
+
legs: legacy.legs.map((leg) => ({
|
|
45454
|
+
...leg,
|
|
45455
|
+
from: legacy.from,
|
|
45456
|
+
purpose: "send"
|
|
45457
|
+
}))
|
|
45458
|
+
});
|
|
45459
|
+
}
|
|
45460
|
+
async function bindLegAddresses(leg, deps) {
|
|
45461
|
+
const fromAddress = await resolveAccountAddress(leg.from, deps);
|
|
45462
|
+
if (fromAddress === void 0) {
|
|
45463
|
+
throw new MovementError(
|
|
45464
|
+
"invalid_movement",
|
|
45465
|
+
`Cannot plan a movement from ${leg.from}: its local address is unavailable. Restore that account, then retry.`
|
|
45466
|
+
);
|
|
45467
|
+
}
|
|
45468
|
+
const toAddress = await resolveRecipientAddress(leg.from, leg.to, deps);
|
|
45469
|
+
if (toAddress === void 0) {
|
|
45470
|
+
throw new MovementError(
|
|
45471
|
+
"invalid_movement",
|
|
45472
|
+
`Cannot plan a movement to ${leg.to}: its address is unavailable. Restore or relink that account, then retry.`
|
|
45473
|
+
);
|
|
45474
|
+
}
|
|
45475
|
+
return { ...leg, fromAddress, toAddress };
|
|
45476
|
+
}
|
|
45477
|
+
async function assertLegAddressBinding(movement, leg, deps) {
|
|
45478
|
+
if (leg.fromAddress === void 0 || leg.toAddress === void 0) {
|
|
45479
|
+
throw movementAddressUnboundError(movement);
|
|
45480
|
+
}
|
|
45481
|
+
const fromAddress = await resolveAccountAddress(leg.from, deps);
|
|
45482
|
+
if (fromAddress === void 0 || !sameAddress2(fromAddress, leg.fromAddress)) {
|
|
45483
|
+
throw new MovementError(
|
|
45484
|
+
"account_address_mismatch",
|
|
45485
|
+
`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}.`,
|
|
45486
|
+
movement.id
|
|
45487
|
+
);
|
|
45488
|
+
}
|
|
45489
|
+
const toAddress = await resolveRecipientAddress(leg.from, leg.to, deps);
|
|
45490
|
+
if (toAddress === void 0 || !sameAddress2(toAddress, leg.toAddress)) {
|
|
45491
|
+
throw new MovementError(
|
|
45492
|
+
"account_address_mismatch",
|
|
45493
|
+
`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}.`,
|
|
45494
|
+
movement.id
|
|
45495
|
+
);
|
|
45496
|
+
}
|
|
45497
|
+
}
|
|
45498
|
+
function movementAddressUnboundError(movement) {
|
|
45499
|
+
return new MovementError(
|
|
45500
|
+
"movement_address_unbound",
|
|
45501
|
+
`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.`,
|
|
45502
|
+
movement.id
|
|
45503
|
+
);
|
|
45504
|
+
}
|
|
45505
|
+
async function resolveAccountAddress(account, deps) {
|
|
45506
|
+
const raw = await (deps.addressReader ?? ((name) => deps.store.readAddress(name)))(account);
|
|
45507
|
+
return raw !== void 0 && isAddress(raw, { strict: false }) ? getAddress(raw) : void 0;
|
|
45508
|
+
}
|
|
45509
|
+
async function resolveRecipientAddress(from15, recipient, deps) {
|
|
45510
|
+
const requested = recipient.trim();
|
|
45511
|
+
if (isAddress(requested, { strict: false })) return getAddress(requested);
|
|
45512
|
+
if (requested === "owner") {
|
|
45513
|
+
const owner = deps.store.entry(from15)?.link?.owner;
|
|
45514
|
+
if (owner !== void 0 && isAddress(owner, { strict: false })) return getAddress(owner);
|
|
45515
|
+
} else {
|
|
45516
|
+
const account = walletNameSchema.safeParse(requested);
|
|
45517
|
+
if (account.success) {
|
|
45518
|
+
const local = await resolveAccountAddress(account.data, deps);
|
|
45519
|
+
if (local !== void 0) return local;
|
|
45520
|
+
}
|
|
45521
|
+
}
|
|
45522
|
+
const resolved = await deps.recipientAddressReader?.(from15, requested);
|
|
45523
|
+
return resolved !== void 0 && isAddress(resolved, { strict: false }) ? getAddress(resolved) : void 0;
|
|
45524
|
+
}
|
|
45525
|
+
function sameAddress2(left, right) {
|
|
45526
|
+
return isAddress(left, { strict: false }) && isAddress(right, { strict: false }) && getAddress(left) === getAddress(right);
|
|
45527
|
+
}
|
|
45528
|
+
var SweepParentUnknownError = class extends Error {
|
|
45529
|
+
};
|
|
45530
|
+
function isOwnerSweep(to) {
|
|
45531
|
+
return to.trim() === "owner" || isAddress(to, { strict: false });
|
|
45532
|
+
}
|
|
45533
|
+
async function readSweepParent(movement, leg, deps) {
|
|
45534
|
+
if (leg.purpose !== "sweep" || isOwnerSweep(leg.to)) return void 0;
|
|
45535
|
+
if (movement.treasury === void 0 || leg.to !== movement.treasury) {
|
|
45536
|
+
throw new SweepParentUnknownError();
|
|
45537
|
+
}
|
|
45538
|
+
const account = walletNameSchema.safeParse(leg.to);
|
|
45539
|
+
if (!account.success) throw new SweepParentUnknownError();
|
|
45540
|
+
const address2 = deps.addressReader === void 0 ? await deps.store.readAddress(account.data) : await deps.addressReader(account.data);
|
|
45541
|
+
if (address2 === void 0 || !isAddress(address2, { strict: false })) {
|
|
45542
|
+
throw new SweepParentUnknownError();
|
|
45543
|
+
}
|
|
45544
|
+
return address2;
|
|
45545
|
+
}
|
|
45546
|
+
function parseMovementId(value) {
|
|
45547
|
+
const parsed = movementIdSchema.safeParse(value);
|
|
45548
|
+
if (!parsed.success) throw new MovementError("invalid_movement", "The movement id is invalid.");
|
|
45549
|
+
return parsed.data;
|
|
45550
|
+
}
|
|
45551
|
+
function movementsDirectory(home) {
|
|
45552
|
+
return join5(home, "movements");
|
|
45553
|
+
}
|
|
45554
|
+
function createMovementNonce() {
|
|
45555
|
+
return bytesToHex(randomBytes6(32));
|
|
45556
|
+
}
|
|
45557
|
+
async function writeMovement(home, movement) {
|
|
45558
|
+
const parsed = movementSchema.parse(movement);
|
|
45559
|
+
const directory = movementsDirectory(home);
|
|
45560
|
+
await mkdir10(directory, { recursive: true, mode: 448 });
|
|
45561
|
+
await chmod3(directory, 448);
|
|
45562
|
+
const path = join5(directory, `${parsed.id}.json`);
|
|
45563
|
+
const temporary = join5(
|
|
45564
|
+
directory,
|
|
45565
|
+
`.${parsed.id}.${process.pid}.${randomBytes6(6).toString("hex")}.tmp`
|
|
45566
|
+
);
|
|
45567
|
+
let handle;
|
|
45568
|
+
try {
|
|
45569
|
+
handle = await open9(temporary, "wx", 384);
|
|
45570
|
+
await handle.writeFile(serializeMovement(parsed), "utf8");
|
|
45571
|
+
await handle.sync();
|
|
45572
|
+
await handle.close();
|
|
45573
|
+
handle = void 0;
|
|
45574
|
+
await rename6(temporary, path);
|
|
45575
|
+
const directoryHandle = await open9(directory, "r");
|
|
45576
|
+
try {
|
|
45577
|
+
await directoryHandle.sync();
|
|
45578
|
+
} finally {
|
|
45579
|
+
await directoryHandle.close();
|
|
45580
|
+
}
|
|
45581
|
+
} catch (error61) {
|
|
45582
|
+
await handle?.close().catch(() => void 0);
|
|
45583
|
+
await unlink7(temporary).catch(() => void 0);
|
|
45584
|
+
throw error61;
|
|
45585
|
+
}
|
|
45586
|
+
}
|
|
45587
|
+
function serializeMovement(movement) {
|
|
45588
|
+
return `${JSON.stringify(movementSchema.parse(movement), null, 2)}
|
|
45589
|
+
`;
|
|
45590
|
+
}
|
|
45591
|
+
|
|
45592
|
+
// src/sweep.ts
|
|
45593
|
+
var ERC20_ABI = [
|
|
45594
|
+
{
|
|
45595
|
+
type: "function",
|
|
45596
|
+
name: "balanceOf",
|
|
45597
|
+
stateMutability: "view",
|
|
45598
|
+
inputs: [{ name: "account", type: "address" }],
|
|
45599
|
+
outputs: [{ name: "balance", type: "uint256" }]
|
|
45600
|
+
},
|
|
45601
|
+
{
|
|
45602
|
+
type: "function",
|
|
45603
|
+
name: "transfer",
|
|
45604
|
+
stateMutability: "nonpayable",
|
|
45605
|
+
inputs: [
|
|
45606
|
+
{ name: "to", type: "address" },
|
|
45607
|
+
{ name: "amount", type: "uint256" }
|
|
45608
|
+
],
|
|
45609
|
+
outputs: [{ name: "success", type: "bool" }]
|
|
45610
|
+
}
|
|
45611
|
+
];
|
|
45612
|
+
async function readUsdcBalance(args) {
|
|
45613
|
+
if (isSolanaNetwork(args.network)) {
|
|
45614
|
+
return await readSolanaUsdcBalance({
|
|
45615
|
+
network: args.network,
|
|
45616
|
+
configured: args.configured,
|
|
45617
|
+
address: args.address,
|
|
45618
|
+
allowPrivateNetwork: args.allowPrivateNetwork,
|
|
45619
|
+
...args.fetchImpl ? { fetchImpl: args.fetchImpl } : {},
|
|
45620
|
+
...args.lookup ? { lookup: args.lookup } : {}
|
|
45621
|
+
});
|
|
45622
|
+
}
|
|
45623
|
+
const client = createNetworkPublicClient(args.network, args.configured, {
|
|
45624
|
+
allowPrivateNetwork: args.allowPrivateNetwork,
|
|
45625
|
+
...args.fetchImpl ? { fetch: args.fetchImpl } : {},
|
|
45626
|
+
...args.lookup ? { lookup: args.lookup } : {}
|
|
45627
|
+
});
|
|
45628
|
+
return await client.readContract({
|
|
45629
|
+
address: getAddress(
|
|
45630
|
+
usesUsdcGas(args.network) ? getNetworkDefinition(args.network).usdc : args.configured.usdc
|
|
45631
|
+
),
|
|
45632
|
+
abi: ERC20_ABI,
|
|
45633
|
+
functionName: "balanceOf",
|
|
45634
|
+
args: [getAddress(args.address)]
|
|
45635
|
+
});
|
|
45636
|
+
}
|
|
45637
|
+
|
|
44381
45638
|
// src/ceiling.ts
|
|
44382
45639
|
var CEILING_SWEEP_MINIMUM_ATOMIC = 100000n;
|
|
44383
45640
|
var CEILING_SWEEP_GUARD_MS = 10 * 60 * 1e3;
|
|
44384
45641
|
var CEILING_SWEEP_TIMEOUT_MS = 1e4;
|
|
44385
45642
|
function sweepTarget(account) {
|
|
44386
|
-
return account.owner;
|
|
45643
|
+
return account.parent?.address ?? account.owner;
|
|
44387
45644
|
}
|
|
44388
45645
|
async function sweepAboveCeiling(args) {
|
|
44389
45646
|
try {
|
|
@@ -44410,6 +45667,25 @@ async function sweepAboveCeilingLocked(args) {
|
|
|
44410
45667
|
if (caps.effectiveCeilingAtomic === null) {
|
|
44411
45668
|
return skipped(args.account, "ceiling_off");
|
|
44412
45669
|
}
|
|
45670
|
+
const unresolvedMovement = await reconcileUnresolvedMovementsForAccountLocked(
|
|
45671
|
+
auditHome,
|
|
45672
|
+
args.account,
|
|
45673
|
+
{
|
|
45674
|
+
store: args.store,
|
|
45675
|
+
secrets: args.secrets,
|
|
45676
|
+
apiBase: args.apiBase,
|
|
45677
|
+
home: auditHome,
|
|
45678
|
+
...args.transfer === void 0 ? {} : { transfer: args.transfer },
|
|
45679
|
+
...args.addressReader === void 0 ? {} : { addressReader: args.addressReader },
|
|
45680
|
+
...args.fetchImpl === void 0 ? {} : { fetchImpl: args.fetchImpl },
|
|
45681
|
+
...args.now === void 0 ? {} : { now: args.now },
|
|
45682
|
+
...args.timeoutMs === void 0 ? {} : { timeoutMs: args.timeoutMs }
|
|
45683
|
+
}
|
|
45684
|
+
);
|
|
45685
|
+
if (unresolvedMovement !== void 0) {
|
|
45686
|
+
await auditFailure(auditHome, args.account, void 0, "unfinished_movement", now);
|
|
45687
|
+
return skipped(args.account, "unfinished_movement");
|
|
45688
|
+
}
|
|
44413
45689
|
const rawAddress = await (args.addressReader ?? ((account) => args.store.readAddress(account)))(
|
|
44414
45690
|
args.account
|
|
44415
45691
|
);
|
|
@@ -44436,12 +45712,12 @@ async function sweepAboveCeilingLocked(args) {
|
|
|
44436
45712
|
}
|
|
44437
45713
|
throw error61;
|
|
44438
45714
|
}
|
|
44439
|
-
const self = siblingResult.siblings.find((candidate) => candidate.self) ?? siblingResult.siblings.find((candidate) =>
|
|
45715
|
+
const self = siblingResult.siblings.find((candidate) => candidate.self) ?? siblingResult.siblings.find((candidate) => sameAddress3(candidate.address, address2));
|
|
44440
45716
|
if (self?.status === "revoked") return skipped(args.account, "revoked");
|
|
44441
45717
|
if (siblingResult.owner === null) return skipped(args.account, "no_known_owner");
|
|
44442
45718
|
const serverOwner = siblingResult.owner;
|
|
44443
45719
|
const linkedOwner = isAddress(entry.link.owner, { strict: false }) ? getAddress(entry.link.owner) : void 0;
|
|
44444
|
-
if (linkedOwner === void 0 || !
|
|
45720
|
+
if (linkedOwner === void 0 || !sameAddress3(linkedOwner, serverOwner)) {
|
|
44445
45721
|
safeWarn(
|
|
44446
45722
|
args.warn,
|
|
44447
45723
|
`Refusing to sweep ${args.account}: its stored owner differs from the linked server owner.`
|
|
@@ -44469,12 +45745,30 @@ async function sweepAboveCeilingLocked(args) {
|
|
|
44469
45745
|
CEILING_SWEEP_GUARD_MS
|
|
44470
45746
|
);
|
|
44471
45747
|
if (!claimed) return skipped(args.account, "throttled");
|
|
45748
|
+
if (pending !== void 0) {
|
|
45749
|
+
if (!sameAddress3(pending.owner, owner)) {
|
|
45750
|
+
return {
|
|
45751
|
+
account: args.account,
|
|
45752
|
+
status: "unknown",
|
|
45753
|
+
reason: "pending_owner_changed",
|
|
45754
|
+
amountUsd: formatAtomicUsd(BigInt(pending.amountAtomic))
|
|
45755
|
+
};
|
|
45756
|
+
}
|
|
45757
|
+
}
|
|
45758
|
+
const parent = await args.resolveParent?.(args.account);
|
|
45759
|
+
const target = sweepTarget({
|
|
45760
|
+
account: args.account,
|
|
45761
|
+
owner,
|
|
45762
|
+
...parent === void 0 ? {} : { parent }
|
|
45763
|
+
});
|
|
44472
45764
|
if (pending !== void 0) {
|
|
44473
45765
|
const reconciled = await executePendingSweep({
|
|
44474
45766
|
args,
|
|
44475
45767
|
pending,
|
|
44476
45768
|
address: address2,
|
|
44477
45769
|
owner,
|
|
45770
|
+
target,
|
|
45771
|
+
sweepParent: parent?.address,
|
|
44478
45772
|
readBalance: readBalance2
|
|
44479
45773
|
});
|
|
44480
45774
|
if (reconciled !== void 0) return reconciled;
|
|
@@ -44482,7 +45776,7 @@ async function sweepAboveCeilingLocked(args) {
|
|
|
44482
45776
|
}
|
|
44483
45777
|
await args.store.reload();
|
|
44484
45778
|
const currentEntry = args.store.entry(args.account);
|
|
44485
|
-
if (currentEntry?.link === void 0 || !sameOrigin(currentEntry.link.apiBase, args.apiBase) || !
|
|
45779
|
+
if (currentEntry?.link === void 0 || !sameOrigin(currentEntry.link.apiBase, args.apiBase) || !sameAddress3(currentEntry.link.owner, owner)) {
|
|
44486
45780
|
return skipped(args.account, "not_linked");
|
|
44487
45781
|
}
|
|
44488
45782
|
const currentCeiling = args.store.ceilingCaps(args.account).effectiveCeilingAtomic;
|
|
@@ -44498,8 +45792,9 @@ async function sweepAboveCeilingLocked(args) {
|
|
|
44498
45792
|
args.signal?.throwIfAborted();
|
|
44499
45793
|
pending = {
|
|
44500
45794
|
owner,
|
|
45795
|
+
target,
|
|
44501
45796
|
amountAtomic: amountAtomic.toString(),
|
|
44502
|
-
nonce: (args.randomNonce ?? (() => bytesToHex(
|
|
45797
|
+
nonce: (args.randomNonce ?? (() => bytesToHex(randomBytes7(32))))(),
|
|
44503
45798
|
createdAt: new Date(now()).toISOString(),
|
|
44504
45799
|
status: "planned"
|
|
44505
45800
|
};
|
|
@@ -44513,6 +45808,8 @@ async function sweepAboveCeilingLocked(args) {
|
|
|
44513
45808
|
pending: stored,
|
|
44514
45809
|
address: address2,
|
|
44515
45810
|
owner,
|
|
45811
|
+
target,
|
|
45812
|
+
sweepParent: parent?.address,
|
|
44516
45813
|
readBalance: readBalance2
|
|
44517
45814
|
}) ?? skipped(args.account, "below_threshold");
|
|
44518
45815
|
} catch (error61) {
|
|
@@ -44522,22 +45819,28 @@ async function sweepAboveCeilingLocked(args) {
|
|
|
44522
45819
|
}
|
|
44523
45820
|
}
|
|
44524
45821
|
async function executePendingSweep(input2) {
|
|
44525
|
-
const {
|
|
45822
|
+
const {
|
|
45823
|
+
args,
|
|
45824
|
+
pending,
|
|
45825
|
+
address: address2,
|
|
45826
|
+
owner,
|
|
45827
|
+
target: currentTarget,
|
|
45828
|
+
sweepParent: currentSweepParent,
|
|
45829
|
+
readBalance: readBalance2
|
|
45830
|
+
} = input2;
|
|
44526
45831
|
const now = args.now ?? Date.now;
|
|
44527
45832
|
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
45833
|
const amountAtomic = BigInt(pending.amountAtomic);
|
|
44537
45834
|
const amountUsd = formatAtomicUsd(amountAtomic);
|
|
44538
|
-
const target = sweepTarget({ account: args.account, owner });
|
|
44539
45835
|
const hasJournal = await hasTransferJournal(auditHome, args.account, pending.nonce);
|
|
44540
45836
|
const resume = pending.status === "unknown" || hasJournal;
|
|
45837
|
+
const storedTarget = getAddress(pending.target ?? owner);
|
|
45838
|
+
if (!resume && !sameAddress3(storedTarget, currentTarget)) {
|
|
45839
|
+
await clearPendingSweep(args.store, args.account, pending.nonce);
|
|
45840
|
+
return void 0;
|
|
45841
|
+
}
|
|
45842
|
+
const target = resume ? storedTarget : currentTarget;
|
|
45843
|
+
const sweepParent = resume ? !sameAddress3(target, owner) ? target : void 0 : currentSweepParent;
|
|
44541
45844
|
try {
|
|
44542
45845
|
const result = await (args.transfer ?? transferBetweenAccounts)({
|
|
44543
45846
|
store: args.store,
|
|
@@ -44548,6 +45851,7 @@ async function executePendingSweep(input2) {
|
|
|
44548
45851
|
amountUsd,
|
|
44549
45852
|
network: "base",
|
|
44550
45853
|
purpose: "sweep",
|
|
45854
|
+
...sweepParent === void 0 ? {} : { sweepParent },
|
|
44551
45855
|
...resume ? { resume: pending.nonce } : { nonce: pending.nonce },
|
|
44552
45856
|
unlock: guardedSweepUnlock(args, address2, owner, amountAtomic, readBalance2),
|
|
44553
45857
|
...args.fetchImpl === void 0 ? {} : { fetchImpl: args.fetchImpl },
|
|
@@ -44685,7 +45989,7 @@ function guardedSweepUnlock(args, address2, owner, amountAtomic, readBalance2) {
|
|
|
44685
45989
|
args.signal?.throwIfAborted();
|
|
44686
45990
|
await args.store.reload();
|
|
44687
45991
|
const entry = args.store.entry(account);
|
|
44688
|
-
if (entry?.link === void 0 || !sameOrigin(entry.link.apiBase, args.apiBase) || !
|
|
45992
|
+
if (entry?.link === void 0 || !sameOrigin(entry.link.apiBase, args.apiBase) || !sameAddress3(entry.link.owner, owner)) {
|
|
44689
45993
|
throw new CeilingPolicyChangedError();
|
|
44690
45994
|
}
|
|
44691
45995
|
const ceiling = args.store.ceilingCaps(account).effectiveCeilingAtomic;
|
|
@@ -44751,77 +46055,17 @@ var CeilingPolicyChangedError = class extends Error {
|
|
|
44751
46055
|
code = "policy_changed";
|
|
44752
46056
|
};
|
|
44753
46057
|
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
46058
|
try {
|
|
44783
|
-
return await
|
|
44784
|
-
|
|
44785
|
-
|
|
44786
|
-
|
|
44787
|
-
|
|
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;
|
|
46059
|
+
return await withAccountMovementLock(
|
|
46060
|
+
args.store.home,
|
|
46061
|
+
args.account,
|
|
46062
|
+
args.timeoutMs ?? CEILING_SWEEP_TIMEOUT_MS,
|
|
46063
|
+
operation,
|
|
46064
|
+
args.signal
|
|
46065
|
+
);
|
|
44823
46066
|
} catch (error61) {
|
|
44824
|
-
|
|
46067
|
+
if (error61 instanceof AccountMovementLockedError) throw new CeilingSweepTimeoutError();
|
|
46068
|
+
throw error61;
|
|
44825
46069
|
}
|
|
44826
46070
|
}
|
|
44827
46071
|
function defaultBalanceReader(args) {
|
|
@@ -44868,7 +46112,7 @@ function sameOrigin(left, right) {
|
|
|
44868
46112
|
return false;
|
|
44869
46113
|
}
|
|
44870
46114
|
}
|
|
44871
|
-
function
|
|
46115
|
+
function sameAddress3(left, right) {
|
|
44872
46116
|
return isAddress(left, { strict: false }) && isAddress(right, { strict: false }) && getAddress(left) === getAddress(right);
|
|
44873
46117
|
}
|
|
44874
46118
|
function safeWarn(warn, message) {
|
|
@@ -44903,11 +46147,160 @@ async function safeAppendAudit(home, record2, now) {
|
|
|
44903
46147
|
await appendAudit(home, record2, { now: () => new Date(now()) }).catch(() => void 0);
|
|
44904
46148
|
}
|
|
44905
46149
|
|
|
46150
|
+
// src/swarm.ts
|
|
46151
|
+
import { readdir as readdir4, readFile as readFile10, stat as stat8, unlink as unlink8 } from "node:fs/promises";
|
|
46152
|
+
import { join as join6 } from "node:path";
|
|
46153
|
+
var SWARM_NAME_PATTERN = /^[a-z][a-z0-9-]{0,15}$/;
|
|
46154
|
+
var SWARM_ROLE_PATTERN = /^[a-z][a-z0-9]{0,7}$/;
|
|
46155
|
+
var atomicSchema = external_exports.string().regex(/^\d+$/);
|
|
46156
|
+
var addressSchema2 = external_exports.string().regex(/^0x[0-9a-fA-F]{40}$/);
|
|
46157
|
+
var capsSchema = external_exports.strictObject({ perCallAtomic: atomicSchema, perDayAtomic: atomicSchema });
|
|
46158
|
+
var movementIdSchema2 = external_exports.string().regex(/^mv_[A-Za-z0-9_-]{8,128}$/u);
|
|
46159
|
+
var amountUsdSchema = external_exports.string().regex(/^\d+\.\d{2}$/u);
|
|
46160
|
+
var setupFundingStatusSchema = external_exports.enum(["planned", "sent", "incomplete"]);
|
|
46161
|
+
var blockedFundingLegSchema = external_exports.strictObject({
|
|
46162
|
+
to: external_exports.string().trim().min(1),
|
|
46163
|
+
amountUsd: amountUsdSchema,
|
|
46164
|
+
reason: external_exports.string().trim().min(1)
|
|
46165
|
+
});
|
|
46166
|
+
var setupFundingSchema = external_exports.strictObject({
|
|
46167
|
+
amountUsd: amountUsdSchema,
|
|
46168
|
+
from: walletNameSchema,
|
|
46169
|
+
movementId: movementIdSchema2.optional(),
|
|
46170
|
+
status: setupFundingStatusSchema,
|
|
46171
|
+
policy: external_exports.strictObject({
|
|
46172
|
+
strategy: external_exports.enum(["targets", "even", "weights"]),
|
|
46173
|
+
movementId: movementIdSchema2.optional(),
|
|
46174
|
+
status: setupFundingStatusSchema,
|
|
46175
|
+
blocked: external_exports.array(blockedFundingLegSchema).optional()
|
|
46176
|
+
}).optional()
|
|
46177
|
+
});
|
|
46178
|
+
var accountSteps = {
|
|
46179
|
+
creating: external_exports.boolean().default(false),
|
|
46180
|
+
created: external_exports.boolean(),
|
|
46181
|
+
capped: external_exports.boolean(),
|
|
46182
|
+
linked: external_exports.boolean()
|
|
46183
|
+
};
|
|
46184
|
+
var treasurySchema = external_exports.strictObject({
|
|
46185
|
+
account: walletNameSchema,
|
|
46186
|
+
// A planned account has no address until its vault derivation succeeds.
|
|
46187
|
+
address: addressSchema2.optional(),
|
|
46188
|
+
steps: external_exports.strictObject(accountSteps)
|
|
46189
|
+
}).refine(
|
|
46190
|
+
(value) => !value.steps.created || value.address !== void 0,
|
|
46191
|
+
"Created accounts require an address."
|
|
46192
|
+
);
|
|
46193
|
+
var memberSchema = external_exports.strictObject({
|
|
46194
|
+
account: walletNameSchema,
|
|
46195
|
+
address: addressSchema2.optional(),
|
|
46196
|
+
role: external_exports.string().regex(SWARM_ROLE_PATTERN),
|
|
46197
|
+
weight: external_exports.number().int().positive(),
|
|
46198
|
+
targetAtomic: atomicSchema,
|
|
46199
|
+
ceilingAtomic: atomicSchema.nullable(),
|
|
46200
|
+
steps: external_exports.strictObject({ ...accountSteps, profiled: external_exports.boolean() })
|
|
46201
|
+
}).refine(
|
|
46202
|
+
(value) => !value.steps.created || value.address !== void 0,
|
|
46203
|
+
"Created accounts require an address."
|
|
46204
|
+
);
|
|
46205
|
+
var swarmFileSchema = external_exports.strictObject({
|
|
46206
|
+
v: external_exports.literal(1),
|
|
46207
|
+
name: external_exports.string().regex(SWARM_NAME_PATTERN),
|
|
46208
|
+
device: external_exports.string().regex(DEVICE_NAME_PATTERN),
|
|
46209
|
+
network: external_exports.enum([BASE_MAINNET_CAIP2, ARC_MAINNET_CAIP2]),
|
|
46210
|
+
createdAt: external_exports.iso.datetime(),
|
|
46211
|
+
treasury: treasurySchema,
|
|
46212
|
+
members: external_exports.array(memberSchema),
|
|
46213
|
+
setupFunding: setupFundingSchema.optional(),
|
|
46214
|
+
policy: external_exports.strictObject({
|
|
46215
|
+
strategy: external_exports.enum(["targets", "even", "weights"]),
|
|
46216
|
+
treasuryCaps: capsSchema
|
|
46217
|
+
})
|
|
46218
|
+
}).refine(
|
|
46219
|
+
(value) => (/* @__PURE__ */ new Set([value.treasury.account, ...value.members.map((member) => member.account)])).size === value.members.length + 1,
|
|
46220
|
+
"Swarm accounts must be unique."
|
|
46221
|
+
);
|
|
46222
|
+
var SwarmError = class extends Error {
|
|
46223
|
+
constructor(code, message) {
|
|
46224
|
+
super(message);
|
|
46225
|
+
this.code = code;
|
|
46226
|
+
this.name = "SwarmError";
|
|
46227
|
+
}
|
|
46228
|
+
code;
|
|
46229
|
+
};
|
|
46230
|
+
function swarmsDirectory(home) {
|
|
46231
|
+
return join6(home, "swarms");
|
|
46232
|
+
}
|
|
46233
|
+
function swarmFilePath(home, name) {
|
|
46234
|
+
assertName(name);
|
|
46235
|
+
return join6(swarmsDirectory(home), `${name}.json`);
|
|
46236
|
+
}
|
|
46237
|
+
async function readSwarm(home, name) {
|
|
46238
|
+
const path = swarmFilePath(home, name);
|
|
46239
|
+
try {
|
|
46240
|
+
const swarm = parseSwarm(JSON.parse(await readFile10(path, "utf8")));
|
|
46241
|
+
if (swarm.name !== name)
|
|
46242
|
+
throw new SwarmError("invalid_swarm", "Swarm name does not match its file.");
|
|
46243
|
+
return swarm;
|
|
46244
|
+
} catch (error61) {
|
|
46245
|
+
if (isMissingFile(error61)) throw new SwarmError("swarm_not_found", `No swarm named ${name}.`);
|
|
46246
|
+
if (error61 instanceof SwarmError) throw error61;
|
|
46247
|
+
throw new SwarmError("invalid_swarm", `Invalid swarm file for ${name}.`);
|
|
46248
|
+
}
|
|
46249
|
+
}
|
|
46250
|
+
async function listSwarms(home) {
|
|
46251
|
+
let files;
|
|
46252
|
+
try {
|
|
46253
|
+
files = await readdir4(swarmsDirectory(home));
|
|
46254
|
+
} catch (error61) {
|
|
46255
|
+
if (isMissingFile(error61)) return [];
|
|
46256
|
+
throw error61;
|
|
46257
|
+
}
|
|
46258
|
+
return await Promise.all(
|
|
46259
|
+
files.filter((file2) => file2.endsWith(".json")).sort().map((file2) => readSwarm(home, file2.slice(0, -5)))
|
|
46260
|
+
);
|
|
46261
|
+
}
|
|
46262
|
+
function swarmParentResolver(home) {
|
|
46263
|
+
return async (account) => {
|
|
46264
|
+
const swarms = await listSwarms(home);
|
|
46265
|
+
const matches = swarms.filter(
|
|
46266
|
+
(swarm) => swarm.members.some((member) => member.account === account)
|
|
46267
|
+
);
|
|
46268
|
+
if (matches.length > 1)
|
|
46269
|
+
throw new SwarmError(
|
|
46270
|
+
"account_in_other_swarm",
|
|
46271
|
+
`Account ${account} belongs to multiple swarms.`
|
|
46272
|
+
);
|
|
46273
|
+
const treasury = matches[0]?.treasury;
|
|
46274
|
+
if (treasury === void 0) return void 0;
|
|
46275
|
+
if (treasury.address === void 0)
|
|
46276
|
+
throw new SwarmError("invalid_swarm", "The swarm treasury is not created yet.");
|
|
46277
|
+
return { account: treasury.account, address: treasury.address };
|
|
46278
|
+
};
|
|
46279
|
+
}
|
|
46280
|
+
function parseSwarm(value) {
|
|
46281
|
+
const result = swarmFileSchema.safeParse(value);
|
|
46282
|
+
if (!result.success) throw new SwarmError("invalid_swarm", "Invalid swarm state.");
|
|
46283
|
+
return result.data;
|
|
46284
|
+
}
|
|
46285
|
+
function assertName(name) {
|
|
46286
|
+
if (!SWARM_NAME_PATTERN.test(name))
|
|
46287
|
+
throw new SwarmError(
|
|
46288
|
+
"invalid_swarm",
|
|
46289
|
+
"A swarm name must be 1 to 16 lowercase letters, digits or dashes, starting with a letter."
|
|
46290
|
+
);
|
|
46291
|
+
}
|
|
46292
|
+
|
|
44906
46293
|
// src/x402-pay.ts
|
|
44907
|
-
import { randomUUID as randomUUID2 } from "node:crypto";
|
|
46294
|
+
import { randomBytes as randomBytes8, randomUUID as randomUUID2 } from "node:crypto";
|
|
46295
|
+
|
|
46296
|
+
// src/run-budget.ts
|
|
46297
|
+
var RunBudgetError = class extends Error {
|
|
46298
|
+
name = "RunBudgetError";
|
|
46299
|
+
code = "run_budget_exceeded";
|
|
46300
|
+
};
|
|
44908
46301
|
|
|
44909
46302
|
// src/support-report.ts
|
|
44910
|
-
var VAPI_CLIENT_VERSION = "0.
|
|
46303
|
+
var VAPI_CLIENT_VERSION = "0.8.0";
|
|
44911
46304
|
|
|
44912
46305
|
// src/x402-pay.ts
|
|
44913
46306
|
var X402PaymentError = class extends Error {
|
|
@@ -44926,6 +46319,7 @@ var X402PaymentError = class extends Error {
|
|
|
44926
46319
|
};
|
|
44927
46320
|
async function payRequest(args) {
|
|
44928
46321
|
const startedAt = args.now ?? /* @__PURE__ */ new Date();
|
|
46322
|
+
assertAllowanceCurrent(startedAt, args.allowanceExpiresAt);
|
|
44929
46323
|
const nowMs = args.nowMs ?? (() => performance.now());
|
|
44930
46324
|
const startedMs = nowMs();
|
|
44931
46325
|
const receiptId = randomUUID2();
|
|
@@ -44965,8 +46359,10 @@ async function payRequest(args) {
|
|
|
44965
46359
|
...args.preferNetworks === void 0 ? {} : { preferNetworks: args.preferNetworks },
|
|
44966
46360
|
...args.paymentId === void 0 ? {} : { paymentId: args.paymentId },
|
|
44967
46361
|
now: startedAt,
|
|
46362
|
+
...args.allowanceExpiresAt === void 0 ? {} : { allowanceExpiresAt: args.allowanceExpiresAt },
|
|
44968
46363
|
nowMs,
|
|
44969
46364
|
trace,
|
|
46365
|
+
...args.runBudget === void 0 ? {} : { runBudget: args.runBudget },
|
|
44970
46366
|
...args.ceilingSweep === void 0 ? {} : { ceilingSweep: args.ceilingSweep }
|
|
44971
46367
|
});
|
|
44972
46368
|
response = completed.response;
|
|
@@ -45000,6 +46396,8 @@ async function payRequest(args) {
|
|
|
45000
46396
|
return { response, receipt };
|
|
45001
46397
|
}
|
|
45002
46398
|
async function completeX402Payment(args) {
|
|
46399
|
+
const paymentTime = args.now ?? /* @__PURE__ */ new Date();
|
|
46400
|
+
assertAllowanceCurrent(paymentTime, args.allowanceExpiresAt);
|
|
45003
46401
|
const nowMs = args.nowMs ?? (() => performance.now());
|
|
45004
46402
|
const trace = args.trace;
|
|
45005
46403
|
const quoteStarted = args.quoteStartedMs ?? nowMs();
|
|
@@ -45022,11 +46420,29 @@ async function completeX402Payment(args) {
|
|
|
45022
46420
|
if (trace) trace.capsApplied = true;
|
|
45023
46421
|
assertMaximumAtomic(quote.amountAtomic, args.maxAtomic);
|
|
45024
46422
|
assertMaxPrice(quote.amountAtomic, args.maxPriceUsd);
|
|
45025
|
-
|
|
46423
|
+
const spendReservation = {
|
|
45026
46424
|
ledgerPath: args.ledgerPath ?? getVapiPaths().ledger,
|
|
45027
46425
|
now: args.now,
|
|
45028
46426
|
...args.wallet === void 0 ? {} : { wallet: args.wallet }
|
|
45029
|
-
}
|
|
46427
|
+
};
|
|
46428
|
+
let reservationId;
|
|
46429
|
+
let reservedOn;
|
|
46430
|
+
if (args.runBudget === void 0) {
|
|
46431
|
+
await reserveSpend(quote.amountAtomic, args.caps, spendReservation);
|
|
46432
|
+
} else {
|
|
46433
|
+
reservationId = args.paymentId ?? `x402_${randomBytes8(12).toString("hex")}`;
|
|
46434
|
+
const reservation = await reserveSpend(quote.amountAtomic, args.caps, {
|
|
46435
|
+
...spendReservation,
|
|
46436
|
+
reservationId
|
|
46437
|
+
});
|
|
46438
|
+
reservedOn = reservation.date;
|
|
46439
|
+
try {
|
|
46440
|
+
args.runBudget.reserve(quote.amountAtomic, reservationId);
|
|
46441
|
+
} catch (error61) {
|
|
46442
|
+
await releaseUnsentReservation(args, quote.amountAtomic, reservationId, reservedOn);
|
|
46443
|
+
throw error61;
|
|
46444
|
+
}
|
|
46445
|
+
}
|
|
45030
46446
|
const signStarted = nowMs();
|
|
45031
46447
|
let payment;
|
|
45032
46448
|
try {
|
|
@@ -45037,6 +46453,7 @@ async function completeX402Payment(args) {
|
|
|
45037
46453
|
allowPrivateNetwork: args.config.allowPrivateNetwork ?? false
|
|
45038
46454
|
}),
|
|
45039
46455
|
...args.now === void 0 ? {} : { nowSeconds: Math.floor(args.now.getTime() / 1e3) },
|
|
46456
|
+
...args.allowanceExpiresAt === void 0 ? {} : { maximumValidBeforeSeconds: Math.floor(args.allowanceExpiresAt.getTime() / 1e3) },
|
|
45040
46457
|
...args.paymentId === void 0 ? {} : { paymentId: args.paymentId }
|
|
45041
46458
|
});
|
|
45042
46459
|
const payer2 = isSvmPaymentRequirements(quote.accepted) ? args.account.solana?.address ?? args.account.address : args.account.address;
|
|
@@ -45048,6 +46465,17 @@ async function completeX402Payment(args) {
|
|
|
45048
46465
|
trace.authorization = { from: from15, nonce, validBefore };
|
|
45049
46466
|
}
|
|
45050
46467
|
}
|
|
46468
|
+
} catch (error61) {
|
|
46469
|
+
if (args.runBudget !== void 0 && reservationId !== void 0 && reservedOn !== void 0) {
|
|
46470
|
+
const released = await releaseUnsentReservation(
|
|
46471
|
+
args,
|
|
46472
|
+
quote.amountAtomic,
|
|
46473
|
+
reservationId,
|
|
46474
|
+
reservedOn
|
|
46475
|
+
);
|
|
46476
|
+
if (released) args.runBudget.release(reservationId);
|
|
46477
|
+
}
|
|
46478
|
+
throw error61;
|
|
45051
46479
|
} finally {
|
|
45052
46480
|
if (trace) trace.phases.signMs = elapsed(nowMs, signStarted);
|
|
45053
46481
|
}
|
|
@@ -45182,6 +46610,26 @@ async function completeX402Payment(args) {
|
|
|
45182
46610
|
paidAttempt.finish();
|
|
45183
46611
|
}
|
|
45184
46612
|
}
|
|
46613
|
+
function assertAllowanceCurrent(now, expiresAt) {
|
|
46614
|
+
if (expiresAt === void 0 || now.getTime() < expiresAt.getTime()) return;
|
|
46615
|
+
throw new Error(
|
|
46616
|
+
`Remote allowance expired at ${expiresAt.toISOString()}. Start a new Railway run before paying.`
|
|
46617
|
+
);
|
|
46618
|
+
}
|
|
46619
|
+
async function releaseUnsentReservation(args, amountAtomic, reservationId, reservedOn) {
|
|
46620
|
+
try {
|
|
46621
|
+
await releaseSpend(amountAtomic, {
|
|
46622
|
+
reservedOn,
|
|
46623
|
+
ledgerPath: args.ledgerPath ?? getVapiPaths().ledger,
|
|
46624
|
+
now: args.now,
|
|
46625
|
+
...args.wallet === void 0 ? {} : { wallet: args.wallet },
|
|
46626
|
+
reservationId
|
|
46627
|
+
});
|
|
46628
|
+
return true;
|
|
46629
|
+
} catch {
|
|
46630
|
+
return false;
|
|
46631
|
+
}
|
|
46632
|
+
}
|
|
45185
46633
|
async function selectQuote(args) {
|
|
45186
46634
|
const networks = toX402Networks(args.config.networks);
|
|
45187
46635
|
const waitFor = args.challenge.waitFor.bind(args.challenge);
|
|
@@ -45276,6 +46724,9 @@ function sanitizeThrownError(error61, secrets) {
|
|
|
45276
46724
|
if (error61 instanceof SpendCapError) {
|
|
45277
46725
|
return new SpendCapError(error61.code, message);
|
|
45278
46726
|
}
|
|
46727
|
+
if (error61 instanceof RunBudgetError) {
|
|
46728
|
+
return new RunBudgetError(message);
|
|
46729
|
+
}
|
|
45279
46730
|
if (error61 instanceof X402PaymentError) {
|
|
45280
46731
|
return new X402PaymentError(error61.code, message, error61.possibleSettlement, {
|
|
45281
46732
|
...cause === void 0 ? {} : { cause },
|
|
@@ -45365,6 +46816,7 @@ function directReceipt(args, request, trace, receiptId, startedAt, latencyMs, re
|
|
|
45365
46816
|
resourceUrl: request.url,
|
|
45366
46817
|
method: request.method,
|
|
45367
46818
|
source: args.source ?? "direct",
|
|
46819
|
+
...args.run === void 0 ? {} : { run: args.run },
|
|
45368
46820
|
...trace.quote ? { quote: trace.quote } : {},
|
|
45369
46821
|
...trace.payer && result.outcome !== "declined_policy" ? { payer: trace.payer } : {},
|
|
45370
46822
|
...trace.paymentId ? { paymentId: trace.paymentId } : {},
|
|
@@ -45392,7 +46844,7 @@ function receiptQuote(quote) {
|
|
|
45392
46844
|
};
|
|
45393
46845
|
}
|
|
45394
46846
|
function paymentErrorOutcome(error61) {
|
|
45395
|
-
if (error61 instanceof SpendCapError || error61 instanceof X402Error && error61.code === "max_price_exceeded") {
|
|
46847
|
+
if (error61 instanceof SpendCapError || error61 instanceof RunBudgetError || error61 instanceof X402Error && error61.code === "max_price_exceeded") {
|
|
45396
46848
|
return "declined_policy";
|
|
45397
46849
|
}
|
|
45398
46850
|
if (error61 instanceof X402PaymentError) {
|
|
@@ -45409,7 +46861,7 @@ function errorCode(error61) {
|
|
|
45409
46861
|
return "request_failed";
|
|
45410
46862
|
}
|
|
45411
46863
|
function receiptSafeErrorMessage(error61) {
|
|
45412
|
-
if (error61 instanceof SpendCapError || error61 instanceof X402Error || error61 instanceof X402PaymentError) {
|
|
46864
|
+
if (error61 instanceof SpendCapError || error61 instanceof RunBudgetError || error61 instanceof X402Error || error61 instanceof X402PaymentError) {
|
|
45413
46865
|
return error61.message;
|
|
45414
46866
|
}
|
|
45415
46867
|
return "The x402 request failed before it completed.";
|
|
@@ -45545,9 +46997,9 @@ async function buyRouterBalance(deps, tierUsd) {
|
|
|
45545
46997
|
if (!isRouterTopupTier(tierUsd)) {
|
|
45546
46998
|
throw new RouterClientError("http", "Router balance must use a $1, $5, $20, or $50 tier.");
|
|
45547
46999
|
}
|
|
45548
|
-
const { link, accessToken } = await purchaseCredentialSnapshot(deps);
|
|
47000
|
+
const { link: link2, accessToken } = await purchaseCredentialSnapshot(deps);
|
|
45549
47001
|
const { response, receipt } = await payRequest({
|
|
45550
|
-
url: `${trimTrailingSlashes(
|
|
47002
|
+
url: `${trimTrailingSlashes(link2.apiBase)}/api/router/top-up/${tierUsd}`,
|
|
45551
47003
|
init: {
|
|
45552
47004
|
method: "POST",
|
|
45553
47005
|
headers: { authorization: `Bearer ${accessToken}`, accept: "application/json" }
|
|
@@ -45561,6 +47013,9 @@ async function buyRouterBalance(deps, tierUsd) {
|
|
|
45561
47013
|
maxAtomic: BigInt(tierUsd) * 1000000n,
|
|
45562
47014
|
preferNetworks: ["eip155:8453"],
|
|
45563
47015
|
...deps.now === void 0 ? {} : { now: deps.now },
|
|
47016
|
+
...deps.allowanceExpiresAt === void 0 ? {} : { allowanceExpiresAt: deps.allowanceExpiresAt },
|
|
47017
|
+
...deps.runBudget === void 0 ? {} : { runBudget: deps.runBudget },
|
|
47018
|
+
...deps.run === void 0 ? {} : { run: deps.run },
|
|
45564
47019
|
source: "router.topup",
|
|
45565
47020
|
ceilingSweep: {
|
|
45566
47021
|
account: deps.wallet,
|
|
@@ -45568,10 +47023,11 @@ async function buyRouterBalance(deps, tierUsd) {
|
|
|
45568
47023
|
run: async (signal) => await sweepAboveCeiling({
|
|
45569
47024
|
store: deps.wallets,
|
|
45570
47025
|
secrets: deps.secrets,
|
|
45571
|
-
apiBase:
|
|
47026
|
+
apiBase: link2.apiBase,
|
|
45572
47027
|
account: deps.wallet,
|
|
45573
47028
|
config: deps.config,
|
|
45574
47029
|
signal,
|
|
47030
|
+
resolveParent: swarmParentResolver(deps.wallets.home),
|
|
45575
47031
|
...deps.fetchImpl === void 0 ? {} : { fetchImpl: deps.fetchImpl },
|
|
45576
47032
|
unlock: async () => deps.account
|
|
45577
47033
|
})
|
|
@@ -45590,14 +47046,14 @@ async function buyRouterBalance(deps, tierUsd) {
|
|
|
45590
47046
|
{
|
|
45591
47047
|
event: "router.buy",
|
|
45592
47048
|
wallet: deps.wallet,
|
|
45593
|
-
owner:
|
|
47049
|
+
owner: link2.owner,
|
|
45594
47050
|
tty: Boolean(process.stderr.isTTY),
|
|
45595
47051
|
detail: `$${tierUsd} on ${receipt.quote?.network ?? "eip155:8453"}`
|
|
45596
47052
|
},
|
|
45597
47053
|
deps.now === void 0 ? {} : { now: () => deps.now }
|
|
45598
47054
|
);
|
|
45599
47055
|
try {
|
|
45600
|
-
await ensureBalanceRouterKey(deps,
|
|
47056
|
+
await ensureBalanceRouterKey(deps, link2);
|
|
45601
47057
|
} catch {
|
|
45602
47058
|
throw new RouterClientError(
|
|
45603
47059
|
"http",
|
|
@@ -45636,7 +47092,12 @@ async function routerChat(deps, request) {
|
|
|
45636
47092
|
config: deps.refill.config,
|
|
45637
47093
|
caps,
|
|
45638
47094
|
...deps.refill.paths === void 0 ? {} : { paths: deps.refill.paths },
|
|
45639
|
-
...deps.refill.now === void 0 ? {} : {
|
|
47095
|
+
...deps.refill.now === void 0 ? {} : {
|
|
47096
|
+
now: typeof deps.refill.now === "function" ? deps.refill.now() : deps.refill.now
|
|
47097
|
+
},
|
|
47098
|
+
...deps.refill.allowanceExpiresAt === void 0 ? {} : { allowanceExpiresAt: deps.refill.allowanceExpiresAt },
|
|
47099
|
+
...deps.refill.runBudget === void 0 ? {} : { runBudget: deps.refill.runBudget },
|
|
47100
|
+
...deps.refill.run === void 0 ? {} : { run: deps.refill.run }
|
|
45640
47101
|
},
|
|
45641
47102
|
refill.tierUsd
|
|
45642
47103
|
)).balance;
|
|
@@ -45648,6 +47109,7 @@ async function routerChat(deps, request) {
|
|
|
45648
47109
|
tty: Boolean(process.stderr.isTTY),
|
|
45649
47110
|
detail: refillDeclinedDetail(refillError)
|
|
45650
47111
|
});
|
|
47112
|
+
if (isRunBudgetError(refillError)) throw refillError;
|
|
45651
47113
|
}
|
|
45652
47114
|
}
|
|
45653
47115
|
const mayHaveBalance = usage === void 0 || (balance?.remainingUsd ?? 0) > 0;
|
|
@@ -45755,8 +47217,8 @@ async function ensureBalanceRouterKey(deps, paidLink) {
|
|
|
45755
47217
|
deps.wallets,
|
|
45756
47218
|
`${paidLink.clientId}:router-balance-key`,
|
|
45757
47219
|
async () => {
|
|
45758
|
-
const
|
|
45759
|
-
if (!sameAgentLink(
|
|
47220
|
+
const link2 = await linkedWallet(deps);
|
|
47221
|
+
if (!sameAgentLink(link2, paidLink)) {
|
|
45760
47222
|
throw new RouterClientError(
|
|
45761
47223
|
"http",
|
|
45762
47224
|
"The agent link changed while fetching the Router balance key."
|
|
@@ -45775,7 +47237,7 @@ async function ensureBalanceRouterKey(deps, paidLink) {
|
|
|
45775
47237
|
"/api/agents/self/router-balance-key",
|
|
45776
47238
|
{ method: "POST" },
|
|
45777
47239
|
"The Router balance key request failed after payment.",
|
|
45778
|
-
|
|
47240
|
+
link2
|
|
45779
47241
|
);
|
|
45780
47242
|
if (!response.ok) {
|
|
45781
47243
|
throw new RouterClientError(
|
|
@@ -45796,7 +47258,7 @@ async function ensureBalanceRouterKey(deps, paidLink) {
|
|
|
45796
47258
|
);
|
|
45797
47259
|
}
|
|
45798
47260
|
const returnedBase = validRouterBaseUrl(parsed.data.router_base_url);
|
|
45799
|
-
const expectedBase = validRouterBaseUrl(
|
|
47261
|
+
const expectedBase = validRouterBaseUrl(link2.routerBaseUrl ?? DEFAULT_ROUTER_BASE_URL);
|
|
45800
47262
|
if (returnedBase !== expectedBase) {
|
|
45801
47263
|
throw new RouterClientError(
|
|
45802
47264
|
"http",
|
|
@@ -45815,8 +47277,8 @@ async function routerBalanceCredentialSnapshot(deps) {
|
|
|
45815
47277
|
const observedLink = await linkedWallet(deps);
|
|
45816
47278
|
try {
|
|
45817
47279
|
return await withAgentCredentialLock(deps.wallets, observedLink.clientId, async () => {
|
|
45818
|
-
const
|
|
45819
|
-
if (
|
|
47280
|
+
const link2 = await linkedWallet(deps);
|
|
47281
|
+
if (link2.clientId !== observedLink.clientId) {
|
|
45820
47282
|
throw new RouterClientError(
|
|
45821
47283
|
"http",
|
|
45822
47284
|
"The agent link changed while reading Router balance credentials."
|
|
@@ -45831,8 +47293,8 @@ async function routerBalanceCredentialSnapshot(deps) {
|
|
|
45831
47293
|
}
|
|
45832
47294
|
if (routerKey === void 0 || routerKey.length === 0) return void 0;
|
|
45833
47295
|
return {
|
|
45834
|
-
clientId:
|
|
45835
|
-
routerBaseUrl: validRouterBaseUrl(
|
|
47296
|
+
clientId: link2.clientId,
|
|
47297
|
+
routerBaseUrl: validRouterBaseUrl(link2.routerBaseUrl ?? DEFAULT_ROUTER_BASE_URL),
|
|
45836
47298
|
routerKey
|
|
45837
47299
|
};
|
|
45838
47300
|
});
|
|
@@ -45847,8 +47309,8 @@ async function routerBalanceCredentialSnapshot(deps) {
|
|
|
45847
47309
|
async function removeRejectedBalanceKey(deps, rejected) {
|
|
45848
47310
|
if (!deps.secrets.available) return;
|
|
45849
47311
|
await withAgentCredentialLock(deps.wallets, rejected.clientId, async () => {
|
|
45850
|
-
const
|
|
45851
|
-
if (
|
|
47312
|
+
const link2 = await linkedWallet(deps);
|
|
47313
|
+
if (link2.clientId !== rejected.clientId) return;
|
|
45852
47314
|
const account = agentSecretAccounts(deps.wallet).routerBalance;
|
|
45853
47315
|
if (await deps.secrets.get(account) === rejected.routerKey) {
|
|
45854
47316
|
await deps.secrets.remove(account);
|
|
@@ -45897,16 +47359,20 @@ function budgetExhaustedError(resetsAt, status) {
|
|
|
45897
47359
|
}
|
|
45898
47360
|
function refillDeclinedDetail(error61) {
|
|
45899
47361
|
if (error61 instanceof SpendCapError) return `${error61.name}: ${error61.code}`;
|
|
47362
|
+
if (isRunBudgetError(error61)) return `RunBudgetError: ${error61.code}`;
|
|
45900
47363
|
if (error61 instanceof RouterClientError) {
|
|
45901
47364
|
return `${error61.name}: ${error61.code}${error61.status === void 0 ? "" : ` (${error61.status})`}`;
|
|
45902
47365
|
}
|
|
45903
47366
|
return "Error: Router balance refill failed.";
|
|
45904
47367
|
}
|
|
47368
|
+
function isRunBudgetError(error61) {
|
|
47369
|
+
return typeof error61 === "object" && error61 !== null && Reflect.get(error61, "code") === "run_budget_exceeded";
|
|
47370
|
+
}
|
|
45905
47371
|
function isRouterTopupTier(value) {
|
|
45906
47372
|
return ROUTER_TOPUP_TIERS.some((tier) => tier === value);
|
|
45907
47373
|
}
|
|
45908
47374
|
async function rotateRouterKey(deps) {
|
|
45909
|
-
const
|
|
47375
|
+
const link2 = await linkedWallet(deps);
|
|
45910
47376
|
if (!deps.secrets.available) {
|
|
45911
47377
|
throw new Error(
|
|
45912
47378
|
"No OS secret store is available on this machine, so the Router key cannot be stored safely."
|
|
@@ -45917,7 +47383,7 @@ async function rotateRouterKey(deps) {
|
|
|
45917
47383
|
"/api/agents/self/router-key",
|
|
45918
47384
|
{ method: "POST" },
|
|
45919
47385
|
"The vAPI Router key rotation request failed.",
|
|
45920
|
-
|
|
47386
|
+
link2
|
|
45921
47387
|
);
|
|
45922
47388
|
if (!response.ok) {
|
|
45923
47389
|
throw new RouterClientError(
|
|
@@ -45932,9 +47398,9 @@ async function rotateRouterKey(deps) {
|
|
|
45932
47398
|
}
|
|
45933
47399
|
validRouterBaseUrl(parsed.data.router_base_url);
|
|
45934
47400
|
try {
|
|
45935
|
-
await withAgentCredentialLock(deps.wallets,
|
|
47401
|
+
await withAgentCredentialLock(deps.wallets, link2.clientId, async () => {
|
|
45936
47402
|
const currentLink = await linkedWallet(deps);
|
|
45937
|
-
if (!sameAgentLink(currentLink,
|
|
47403
|
+
if (!sameAgentLink(currentLink, link2)) {
|
|
45938
47404
|
throw new RouterClientError("http", "The agent link changed during Router key rotation.");
|
|
45939
47405
|
}
|
|
45940
47406
|
const account = agentSecretAccounts(deps.wallet).routerStake;
|
|
@@ -45998,15 +47464,15 @@ async function routerCredentialSnapshot(deps) {
|
|
|
45998
47464
|
const observedLink = await linkedWallet(deps);
|
|
45999
47465
|
try {
|
|
46000
47466
|
return await withAgentCredentialLock(deps.wallets, observedLink.clientId, async () => {
|
|
46001
|
-
const
|
|
46002
|
-
if (
|
|
47467
|
+
const link2 = await linkedWallet(deps);
|
|
47468
|
+
if (link2.clientId !== observedLink.clientId) {
|
|
46003
47469
|
throw new RouterClientError(
|
|
46004
47470
|
"http",
|
|
46005
47471
|
"The agent link changed while reading Router credentials."
|
|
46006
47472
|
);
|
|
46007
47473
|
}
|
|
46008
47474
|
return {
|
|
46009
|
-
routerBaseUrl: validRouterBaseUrl(
|
|
47475
|
+
routerBaseUrl: validRouterBaseUrl(link2.routerBaseUrl ?? DEFAULT_ROUTER_BASE_URL),
|
|
46010
47476
|
routerKey: await storedRouterKey(deps)
|
|
46011
47477
|
};
|
|
46012
47478
|
});
|
|
@@ -46027,9 +47493,9 @@ async function linkedWallet(deps) {
|
|
|
46027
47493
|
} catch {
|
|
46028
47494
|
throw new RouterClientError("http", "The wallet link could not be read.");
|
|
46029
47495
|
}
|
|
46030
|
-
const
|
|
46031
|
-
if (
|
|
46032
|
-
return
|
|
47496
|
+
const link2 = deps.wallets.entry(deps.wallet)?.link;
|
|
47497
|
+
if (link2 === void 0) throw notLinkedError2();
|
|
47498
|
+
return link2;
|
|
46033
47499
|
}
|
|
46034
47500
|
async function storedRouterKey(deps) {
|
|
46035
47501
|
if (!deps.secrets.available) throw noRouterKeyError();
|
|
@@ -46046,7 +47512,7 @@ function sameAgentLink(left, right) {
|
|
|
46046
47512
|
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
47513
|
}
|
|
46048
47514
|
async function consoleRequest(deps, path, init, failureMessage, knownLink) {
|
|
46049
|
-
const
|
|
47515
|
+
const link2 = knownLink ?? await linkedWallet(deps);
|
|
46050
47516
|
try {
|
|
46051
47517
|
return await agentFetch(
|
|
46052
47518
|
{
|
|
@@ -46055,7 +47521,7 @@ async function consoleRequest(deps, path, init, failureMessage, knownLink) {
|
|
|
46055
47521
|
wallet: deps.wallet,
|
|
46056
47522
|
...deps.fetchImpl === void 0 ? {} : { fetchImpl: deps.fetchImpl }
|
|
46057
47523
|
},
|
|
46058
|
-
`${trimTrailingSlashes(
|
|
47524
|
+
`${trimTrailingSlashes(link2.apiBase)}${path}`,
|
|
46059
47525
|
init
|
|
46060
47526
|
);
|
|
46061
47527
|
} catch (error61) {
|
|
@@ -46066,7 +47532,7 @@ async function consoleRequest(deps, path, init, failureMessage, knownLink) {
|
|
|
46066
47532
|
}
|
|
46067
47533
|
}
|
|
46068
47534
|
async function verifyAgentLink(deps) {
|
|
46069
|
-
const
|
|
47535
|
+
const link2 = await linkedWallet(deps);
|
|
46070
47536
|
try {
|
|
46071
47537
|
await agentFetch(
|
|
46072
47538
|
{
|
|
@@ -46075,7 +47541,7 @@ async function verifyAgentLink(deps) {
|
|
|
46075
47541
|
wallet: deps.wallet,
|
|
46076
47542
|
...deps.fetchImpl === void 0 ? {} : { fetchImpl: deps.fetchImpl }
|
|
46077
47543
|
},
|
|
46078
|
-
`${trimTrailingSlashes(
|
|
47544
|
+
`${trimTrailingSlashes(link2.apiBase)}/api/agents/self/router`,
|
|
46079
47545
|
{ method: "GET" }
|
|
46080
47546
|
);
|
|
46081
47547
|
} catch (error61) {
|