@useagentpay/sdk 0.1.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/LICENSE +21 -0
- package/dist/cli.cjs +2758 -0
- package/dist/cli.cjs.map +1 -0
- package/dist/cli.d.cts +1 -0
- package/dist/cli.d.ts +1 -0
- package/dist/cli.js +2728 -0
- package/dist/cli.js.map +1 -0
- package/dist/index.cjs +1046 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +328 -0
- package/dist/index.d.ts +328 -0
- package/dist/index.js +981 -0
- package/dist/index.js.map +1 -0
- package/package.json +48 -0
package/dist/index.js
ADDED
|
@@ -0,0 +1,981 @@
|
|
|
1
|
+
var __defProp = Object.defineProperty;
|
|
2
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
3
|
+
var __esm = (fn, res) => function __init() {
|
|
4
|
+
return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
|
|
5
|
+
};
|
|
6
|
+
var __export = (target, all) => {
|
|
7
|
+
for (var name in all)
|
|
8
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
9
|
+
};
|
|
10
|
+
|
|
11
|
+
// ../../node_modules/.pnpm/tsup@8.5.1_jiti@2.6.1_postcss@8.5.6_tsx@4.21.0_typescript@5.9.3/node_modules/tsup/assets/esm_shims.js
|
|
12
|
+
import path from "path";
|
|
13
|
+
import { fileURLToPath } from "url";
|
|
14
|
+
var init_esm_shims = __esm({
|
|
15
|
+
"../../node_modules/.pnpm/tsup@8.5.1_jiti@2.6.1_postcss@8.5.6_tsx@4.21.0_typescript@5.9.3/node_modules/tsup/assets/esm_shims.js"() {
|
|
16
|
+
"use strict";
|
|
17
|
+
}
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
// src/errors.ts
|
|
21
|
+
var NotSetupError, DecryptError, InsufficientBalanceError, ExceedsTxLimitError, NotApprovedError, InvalidMandateError, AlreadyExecutedError, CheckoutFailedError, TimeoutError;
|
|
22
|
+
var init_errors = __esm({
|
|
23
|
+
"src/errors.ts"() {
|
|
24
|
+
"use strict";
|
|
25
|
+
init_esm_shims();
|
|
26
|
+
NotSetupError = class extends Error {
|
|
27
|
+
code = "NOT_SETUP";
|
|
28
|
+
constructor(message = "AgentPay has not been set up yet. Run `agentpay setup` first.") {
|
|
29
|
+
super(message);
|
|
30
|
+
this.name = "NotSetupError";
|
|
31
|
+
}
|
|
32
|
+
};
|
|
33
|
+
DecryptError = class extends Error {
|
|
34
|
+
code = "DECRYPT_FAILED";
|
|
35
|
+
constructor(message = "Failed to decrypt credentials. Wrong passphrase or corrupted file.") {
|
|
36
|
+
super(message);
|
|
37
|
+
this.name = "DecryptError";
|
|
38
|
+
}
|
|
39
|
+
};
|
|
40
|
+
InsufficientBalanceError = class extends Error {
|
|
41
|
+
code = "INSUFFICIENT_BALANCE";
|
|
42
|
+
constructor(amount, balance) {
|
|
43
|
+
const msg = amount !== void 0 && balance !== void 0 ? `Insufficient balance: requested $${amount.toFixed(2)} but only $${balance.toFixed(2)} available.` : "Insufficient balance for this transaction.";
|
|
44
|
+
super(msg);
|
|
45
|
+
this.name = "InsufficientBalanceError";
|
|
46
|
+
}
|
|
47
|
+
};
|
|
48
|
+
ExceedsTxLimitError = class extends Error {
|
|
49
|
+
code = "EXCEEDS_TX_LIMIT";
|
|
50
|
+
constructor(amount, limit) {
|
|
51
|
+
const msg = amount !== void 0 && limit !== void 0 ? `Amount $${amount.toFixed(2)} exceeds per-transaction limit of $${limit.toFixed(2)}.` : "Amount exceeds per-transaction limit.";
|
|
52
|
+
super(msg);
|
|
53
|
+
this.name = "ExceedsTxLimitError";
|
|
54
|
+
}
|
|
55
|
+
};
|
|
56
|
+
NotApprovedError = class extends Error {
|
|
57
|
+
code = "NOT_APPROVED";
|
|
58
|
+
constructor(txId) {
|
|
59
|
+
super(txId ? `Transaction ${txId} has not been approved.` : "Transaction has not been approved.");
|
|
60
|
+
this.name = "NotApprovedError";
|
|
61
|
+
}
|
|
62
|
+
};
|
|
63
|
+
InvalidMandateError = class extends Error {
|
|
64
|
+
code = "INVALID_MANDATE";
|
|
65
|
+
constructor(message = "Purchase mandate signature verification failed.") {
|
|
66
|
+
super(message);
|
|
67
|
+
this.name = "InvalidMandateError";
|
|
68
|
+
}
|
|
69
|
+
};
|
|
70
|
+
AlreadyExecutedError = class extends Error {
|
|
71
|
+
code = "ALREADY_EXECUTED";
|
|
72
|
+
constructor(txId) {
|
|
73
|
+
super(txId ? `Transaction ${txId} has already been executed.` : "Transaction has already been executed.");
|
|
74
|
+
this.name = "AlreadyExecutedError";
|
|
75
|
+
}
|
|
76
|
+
};
|
|
77
|
+
CheckoutFailedError = class extends Error {
|
|
78
|
+
code = "CHECKOUT_FAILED";
|
|
79
|
+
constructor(message = "Failed to complete checkout.") {
|
|
80
|
+
super(message);
|
|
81
|
+
this.name = "CheckoutFailedError";
|
|
82
|
+
}
|
|
83
|
+
};
|
|
84
|
+
TimeoutError = class extends Error {
|
|
85
|
+
code = "TIMEOUT";
|
|
86
|
+
constructor(message = "Operation timed out.") {
|
|
87
|
+
super(message);
|
|
88
|
+
this.name = "TimeoutError";
|
|
89
|
+
}
|
|
90
|
+
};
|
|
91
|
+
}
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
// src/transactions/poller.ts
|
|
95
|
+
var poller_exports = {};
|
|
96
|
+
__export(poller_exports, {
|
|
97
|
+
waitForApproval: () => waitForApproval
|
|
98
|
+
});
|
|
99
|
+
async function waitForApproval(txId, manager, options) {
|
|
100
|
+
const interval = options?.pollInterval ?? 2e3;
|
|
101
|
+
const timeout = options?.timeout ?? 3e5;
|
|
102
|
+
const deadline = Date.now() + timeout;
|
|
103
|
+
while (Date.now() < deadline) {
|
|
104
|
+
const tx = manager.get(txId);
|
|
105
|
+
if (!tx) throw new Error(`Transaction ${txId} not found.`);
|
|
106
|
+
if (tx.status === "approved") return { status: "approved" };
|
|
107
|
+
if (tx.status === "rejected") return { status: "rejected", reason: tx.rejectionReason };
|
|
108
|
+
await new Promise((resolve) => setTimeout(resolve, interval));
|
|
109
|
+
}
|
|
110
|
+
throw new TimeoutError(`Timed out waiting for approval of ${txId}`);
|
|
111
|
+
}
|
|
112
|
+
var init_poller = __esm({
|
|
113
|
+
"src/transactions/poller.ts"() {
|
|
114
|
+
"use strict";
|
|
115
|
+
init_esm_shims();
|
|
116
|
+
init_errors();
|
|
117
|
+
}
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
// src/index.ts
|
|
121
|
+
init_esm_shims();
|
|
122
|
+
init_errors();
|
|
123
|
+
|
|
124
|
+
// src/utils/ids.ts
|
|
125
|
+
init_esm_shims();
|
|
126
|
+
import { randomBytes } from "crypto";
|
|
127
|
+
function generateTxId() {
|
|
128
|
+
return `tx_${randomBytes(4).toString("hex")}`;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
// src/utils/display.ts
|
|
132
|
+
init_esm_shims();
|
|
133
|
+
function formatCurrency(amount) {
|
|
134
|
+
return `$${amount.toFixed(2)}`;
|
|
135
|
+
}
|
|
136
|
+
function formatTimestamp(iso) {
|
|
137
|
+
const d = new Date(iso);
|
|
138
|
+
return d.toLocaleString("en-US", {
|
|
139
|
+
month: "short",
|
|
140
|
+
day: "numeric",
|
|
141
|
+
hour: "2-digit",
|
|
142
|
+
minute: "2-digit"
|
|
143
|
+
});
|
|
144
|
+
}
|
|
145
|
+
function formatTable(transactions) {
|
|
146
|
+
if (transactions.length === 0) return "No transactions.";
|
|
147
|
+
const header = "TX_ID MERCHANT AMOUNT DESCRIPTION";
|
|
148
|
+
const separator = "\u2500".repeat(header.length);
|
|
149
|
+
const rows = transactions.map((tx) => {
|
|
150
|
+
const id = tx.id.padEnd(14);
|
|
151
|
+
const merchant = tx.merchant.padEnd(16);
|
|
152
|
+
const amount = formatCurrency(tx.amount).padStart(9);
|
|
153
|
+
return `${id}${merchant}${amount} ${tx.description}`;
|
|
154
|
+
});
|
|
155
|
+
return [header, separator, ...rows].join("\n");
|
|
156
|
+
}
|
|
157
|
+
function formatStatus(data) {
|
|
158
|
+
const lines = [
|
|
159
|
+
"AgentPay Status",
|
|
160
|
+
"\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500",
|
|
161
|
+
`Balance: ${formatCurrency(data.balance)} / ${formatCurrency(data.budget)}`,
|
|
162
|
+
`Per-tx limit: ${formatCurrency(data.limitPerTx)}`,
|
|
163
|
+
`Pending purchases: ${data.pending.length}`
|
|
164
|
+
];
|
|
165
|
+
if (data.recent.length > 0) {
|
|
166
|
+
lines.push("", "Recent:");
|
|
167
|
+
for (const tx of data.recent) {
|
|
168
|
+
const status = `[${tx.status}]`.padEnd(12);
|
|
169
|
+
lines.push(` ${status} ${tx.id} ${tx.merchant.padEnd(12)} ${formatCurrency(tx.amount).padStart(8)} ${tx.description}`);
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
return lines.join("\n");
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
// src/utils/paths.ts
|
|
176
|
+
init_esm_shims();
|
|
177
|
+
import { homedir } from "os";
|
|
178
|
+
import { join } from "path";
|
|
179
|
+
function getHomePath() {
|
|
180
|
+
return process.env.AGENTPAY_HOME || join(homedir(), ".agentpay");
|
|
181
|
+
}
|
|
182
|
+
function getCredentialsPath() {
|
|
183
|
+
return join(getHomePath(), "credentials.enc");
|
|
184
|
+
}
|
|
185
|
+
function getKeysPath() {
|
|
186
|
+
return join(getHomePath(), "keys");
|
|
187
|
+
}
|
|
188
|
+
function getPublicKeyPath() {
|
|
189
|
+
return join(getKeysPath(), "public.pem");
|
|
190
|
+
}
|
|
191
|
+
function getPrivateKeyPath() {
|
|
192
|
+
return join(getKeysPath(), "private.pem");
|
|
193
|
+
}
|
|
194
|
+
function getWalletPath() {
|
|
195
|
+
return join(getHomePath(), "wallet.json");
|
|
196
|
+
}
|
|
197
|
+
function getTransactionsPath() {
|
|
198
|
+
return join(getHomePath(), "transactions.json");
|
|
199
|
+
}
|
|
200
|
+
function getAuditPath() {
|
|
201
|
+
return join(getHomePath(), "audit.log");
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
// src/vault/vault.ts
|
|
205
|
+
init_esm_shims();
|
|
206
|
+
init_errors();
|
|
207
|
+
import { pbkdf2Sync, randomBytes as randomBytes2, createCipheriv, createDecipheriv } from "crypto";
|
|
208
|
+
import { readFileSync, writeFileSync, mkdirSync } from "fs";
|
|
209
|
+
import { dirname } from "path";
|
|
210
|
+
var ALGORITHM = "aes-256-gcm";
|
|
211
|
+
var PBKDF2_ITERATIONS = 1e5;
|
|
212
|
+
var PBKDF2_DIGEST = "sha512";
|
|
213
|
+
var KEY_LENGTH = 32;
|
|
214
|
+
var SALT_LENGTH = 32;
|
|
215
|
+
var IV_LENGTH = 16;
|
|
216
|
+
function deriveKey(passphrase, salt) {
|
|
217
|
+
return pbkdf2Sync(passphrase, salt, PBKDF2_ITERATIONS, KEY_LENGTH, PBKDF2_DIGEST);
|
|
218
|
+
}
|
|
219
|
+
function encrypt(credentials, passphrase) {
|
|
220
|
+
const salt = randomBytes2(SALT_LENGTH);
|
|
221
|
+
const iv = randomBytes2(IV_LENGTH);
|
|
222
|
+
const key = deriveKey(passphrase, salt);
|
|
223
|
+
const cipher = createCipheriv(ALGORITHM, key, iv);
|
|
224
|
+
const plaintext = JSON.stringify(credentials);
|
|
225
|
+
const encrypted = Buffer.concat([cipher.update(plaintext, "utf8"), cipher.final()]);
|
|
226
|
+
const authTag = cipher.getAuthTag();
|
|
227
|
+
return {
|
|
228
|
+
ciphertext: Buffer.concat([encrypted, authTag]).toString("base64"),
|
|
229
|
+
salt: salt.toString("base64"),
|
|
230
|
+
iv: iv.toString("base64")
|
|
231
|
+
};
|
|
232
|
+
}
|
|
233
|
+
function decrypt(vault, passphrase) {
|
|
234
|
+
try {
|
|
235
|
+
const salt = Buffer.from(vault.salt, "base64");
|
|
236
|
+
const iv = Buffer.from(vault.iv, "base64");
|
|
237
|
+
const data = Buffer.from(vault.ciphertext, "base64");
|
|
238
|
+
const key = deriveKey(passphrase, salt);
|
|
239
|
+
const authTag = data.subarray(data.length - 16);
|
|
240
|
+
const ciphertext = data.subarray(0, data.length - 16);
|
|
241
|
+
const decipher = createDecipheriv(ALGORITHM, key, iv);
|
|
242
|
+
decipher.setAuthTag(authTag);
|
|
243
|
+
const decrypted = Buffer.concat([decipher.update(ciphertext), decipher.final()]);
|
|
244
|
+
return JSON.parse(decrypted.toString("utf8"));
|
|
245
|
+
} catch {
|
|
246
|
+
throw new DecryptError();
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
function saveVault(vault, path2) {
|
|
250
|
+
const filePath = path2 ?? getCredentialsPath();
|
|
251
|
+
mkdirSync(dirname(filePath), { recursive: true });
|
|
252
|
+
writeFileSync(filePath, JSON.stringify(vault, null, 2), { mode: 384 });
|
|
253
|
+
}
|
|
254
|
+
function loadVault(path2) {
|
|
255
|
+
const filePath = path2 ?? getCredentialsPath();
|
|
256
|
+
try {
|
|
257
|
+
const data = readFileSync(filePath, "utf8");
|
|
258
|
+
return JSON.parse(data);
|
|
259
|
+
} catch {
|
|
260
|
+
throw new NotSetupError();
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
// src/auth/keypair.ts
|
|
265
|
+
init_esm_shims();
|
|
266
|
+
import { generateKeyPairSync } from "crypto";
|
|
267
|
+
import { readFileSync as readFileSync2, writeFileSync as writeFileSync2, mkdirSync as mkdirSync2 } from "fs";
|
|
268
|
+
import { dirname as dirname2 } from "path";
|
|
269
|
+
function generateKeyPair(passphrase) {
|
|
270
|
+
const { publicKey, privateKey } = generateKeyPairSync("ed25519", {
|
|
271
|
+
publicKeyEncoding: { type: "spki", format: "pem" },
|
|
272
|
+
privateKeyEncoding: {
|
|
273
|
+
type: "pkcs8",
|
|
274
|
+
format: "pem",
|
|
275
|
+
cipher: "aes-256-cbc",
|
|
276
|
+
passphrase
|
|
277
|
+
}
|
|
278
|
+
});
|
|
279
|
+
return { publicKey, privateKey };
|
|
280
|
+
}
|
|
281
|
+
function saveKeyPair(keys, publicPath, privatePath) {
|
|
282
|
+
const pubPath = publicPath ?? getPublicKeyPath();
|
|
283
|
+
const privPath = privatePath ?? getPrivateKeyPath();
|
|
284
|
+
mkdirSync2(dirname2(pubPath), { recursive: true });
|
|
285
|
+
writeFileSync2(pubPath, keys.publicKey, { mode: 420 });
|
|
286
|
+
writeFileSync2(privPath, keys.privateKey, { mode: 384 });
|
|
287
|
+
}
|
|
288
|
+
function loadPublicKey(path2) {
|
|
289
|
+
return readFileSync2(path2 ?? getPublicKeyPath(), "utf8");
|
|
290
|
+
}
|
|
291
|
+
function loadPrivateKey(path2) {
|
|
292
|
+
return readFileSync2(path2 ?? getPrivateKeyPath(), "utf8");
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
// src/auth/mandate.ts
|
|
296
|
+
init_esm_shims();
|
|
297
|
+
import { createHash, createPrivateKey, createPublicKey as createPublicKey2, sign, verify } from "crypto";
|
|
298
|
+
function hashTransactionDetails(details) {
|
|
299
|
+
const canonical = JSON.stringify({
|
|
300
|
+
txId: details.txId,
|
|
301
|
+
merchant: details.merchant,
|
|
302
|
+
amount: details.amount,
|
|
303
|
+
description: details.description,
|
|
304
|
+
timestamp: details.timestamp
|
|
305
|
+
});
|
|
306
|
+
return createHash("sha256").update(canonical).digest("hex");
|
|
307
|
+
}
|
|
308
|
+
function createMandate(txDetails, privateKeyPem, passphrase) {
|
|
309
|
+
const txHash = hashTransactionDetails(txDetails);
|
|
310
|
+
const data = Buffer.from(txHash);
|
|
311
|
+
const privateKey = createPrivateKey({
|
|
312
|
+
key: privateKeyPem,
|
|
313
|
+
format: "pem",
|
|
314
|
+
type: "pkcs8",
|
|
315
|
+
passphrase
|
|
316
|
+
});
|
|
317
|
+
const signature = sign(null, data, privateKey);
|
|
318
|
+
const publicKey = createPublicKey2(privateKey);
|
|
319
|
+
const publicKeyPem = publicKey.export({ type: "spki", format: "pem" });
|
|
320
|
+
return {
|
|
321
|
+
txId: txDetails.txId,
|
|
322
|
+
txHash,
|
|
323
|
+
signature: signature.toString("base64"),
|
|
324
|
+
publicKey: publicKeyPem,
|
|
325
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
326
|
+
};
|
|
327
|
+
}
|
|
328
|
+
function verifyMandate(mandate, txDetails) {
|
|
329
|
+
try {
|
|
330
|
+
const txHash = hashTransactionDetails(txDetails);
|
|
331
|
+
if (txHash !== mandate.txHash) return false;
|
|
332
|
+
const data = Buffer.from(txHash);
|
|
333
|
+
const signature = Buffer.from(mandate.signature, "base64");
|
|
334
|
+
const publicKey = createPublicKey2({
|
|
335
|
+
key: mandate.publicKey,
|
|
336
|
+
format: "pem",
|
|
337
|
+
type: "spki"
|
|
338
|
+
});
|
|
339
|
+
return verify(null, data, publicKey, signature);
|
|
340
|
+
} catch {
|
|
341
|
+
return false;
|
|
342
|
+
}
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
// src/budget/budget.ts
|
|
346
|
+
init_esm_shims();
|
|
347
|
+
init_errors();
|
|
348
|
+
import { readFileSync as readFileSync3, writeFileSync as writeFileSync3, mkdirSync as mkdirSync3 } from "fs";
|
|
349
|
+
import { dirname as dirname3 } from "path";
|
|
350
|
+
var BudgetManager = class {
|
|
351
|
+
walletPath;
|
|
352
|
+
constructor(walletPath) {
|
|
353
|
+
this.walletPath = walletPath ?? getWalletPath();
|
|
354
|
+
}
|
|
355
|
+
getWallet() {
|
|
356
|
+
try {
|
|
357
|
+
const data = readFileSync3(this.walletPath, "utf8");
|
|
358
|
+
return JSON.parse(data);
|
|
359
|
+
} catch {
|
|
360
|
+
throw new NotSetupError();
|
|
361
|
+
}
|
|
362
|
+
}
|
|
363
|
+
saveWallet(wallet) {
|
|
364
|
+
mkdirSync3(dirname3(this.walletPath), { recursive: true });
|
|
365
|
+
writeFileSync3(this.walletPath, JSON.stringify(wallet, null, 2), { mode: 384 });
|
|
366
|
+
}
|
|
367
|
+
setBudget(amount) {
|
|
368
|
+
let wallet;
|
|
369
|
+
try {
|
|
370
|
+
wallet = this.getWallet();
|
|
371
|
+
} catch {
|
|
372
|
+
wallet = { budget: 0, balance: 0, limitPerTx: 0, spent: 0 };
|
|
373
|
+
}
|
|
374
|
+
wallet.budget = amount;
|
|
375
|
+
wallet.balance = amount - wallet.spent;
|
|
376
|
+
this.saveWallet(wallet);
|
|
377
|
+
}
|
|
378
|
+
setLimitPerTx(limit) {
|
|
379
|
+
const wallet = this.getWallet();
|
|
380
|
+
wallet.limitPerTx = limit;
|
|
381
|
+
this.saveWallet(wallet);
|
|
382
|
+
}
|
|
383
|
+
deductBalance(amount) {
|
|
384
|
+
const wallet = this.getWallet();
|
|
385
|
+
if (amount > wallet.balance) {
|
|
386
|
+
throw new InsufficientBalanceError(amount, wallet.balance);
|
|
387
|
+
}
|
|
388
|
+
wallet.balance -= amount;
|
|
389
|
+
wallet.spent += amount;
|
|
390
|
+
this.saveWallet(wallet);
|
|
391
|
+
}
|
|
392
|
+
checkProposal(amount) {
|
|
393
|
+
const wallet = this.getWallet();
|
|
394
|
+
if (amount > wallet.balance) {
|
|
395
|
+
throw new InsufficientBalanceError(amount, wallet.balance);
|
|
396
|
+
}
|
|
397
|
+
if (wallet.limitPerTx > 0 && amount > wallet.limitPerTx) {
|
|
398
|
+
throw new ExceedsTxLimitError(amount, wallet.limitPerTx);
|
|
399
|
+
}
|
|
400
|
+
}
|
|
401
|
+
addFunds(amount) {
|
|
402
|
+
const wallet = this.getWallet();
|
|
403
|
+
wallet.budget += amount;
|
|
404
|
+
wallet.balance += amount;
|
|
405
|
+
this.saveWallet(wallet);
|
|
406
|
+
return wallet;
|
|
407
|
+
}
|
|
408
|
+
getBalance() {
|
|
409
|
+
const wallet = this.getWallet();
|
|
410
|
+
return {
|
|
411
|
+
budget: wallet.budget,
|
|
412
|
+
balance: wallet.balance,
|
|
413
|
+
limitPerTx: wallet.limitPerTx,
|
|
414
|
+
spent: wallet.spent
|
|
415
|
+
};
|
|
416
|
+
}
|
|
417
|
+
initWallet(budget, limitPerTx = 0) {
|
|
418
|
+
const wallet = { budget, balance: budget, limitPerTx, spent: 0 };
|
|
419
|
+
this.saveWallet(wallet);
|
|
420
|
+
}
|
|
421
|
+
};
|
|
422
|
+
|
|
423
|
+
// src/transactions/manager.ts
|
|
424
|
+
init_esm_shims();
|
|
425
|
+
import { readFileSync as readFileSync4, writeFileSync as writeFileSync4, mkdirSync as mkdirSync4 } from "fs";
|
|
426
|
+
import { dirname as dirname4 } from "path";
|
|
427
|
+
var TransactionManager = class {
|
|
428
|
+
txPath;
|
|
429
|
+
constructor(txPath) {
|
|
430
|
+
this.txPath = txPath ?? getTransactionsPath();
|
|
431
|
+
}
|
|
432
|
+
loadAll() {
|
|
433
|
+
try {
|
|
434
|
+
const data = readFileSync4(this.txPath, "utf8");
|
|
435
|
+
return JSON.parse(data);
|
|
436
|
+
} catch {
|
|
437
|
+
return [];
|
|
438
|
+
}
|
|
439
|
+
}
|
|
440
|
+
saveAll(transactions) {
|
|
441
|
+
mkdirSync4(dirname4(this.txPath), { recursive: true });
|
|
442
|
+
writeFileSync4(this.txPath, JSON.stringify(transactions, null, 2), { mode: 384 });
|
|
443
|
+
}
|
|
444
|
+
propose(options) {
|
|
445
|
+
const transactions = this.loadAll();
|
|
446
|
+
const tx = {
|
|
447
|
+
id: generateTxId(),
|
|
448
|
+
status: "pending",
|
|
449
|
+
merchant: options.merchant,
|
|
450
|
+
amount: options.amount,
|
|
451
|
+
description: options.description,
|
|
452
|
+
url: options.url,
|
|
453
|
+
createdAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
454
|
+
};
|
|
455
|
+
transactions.push(tx);
|
|
456
|
+
this.saveAll(transactions);
|
|
457
|
+
return tx;
|
|
458
|
+
}
|
|
459
|
+
get(txId) {
|
|
460
|
+
return this.loadAll().find((tx) => tx.id === txId);
|
|
461
|
+
}
|
|
462
|
+
approve(txId, mandate) {
|
|
463
|
+
const transactions = this.loadAll();
|
|
464
|
+
const tx = transactions.find((t) => t.id === txId);
|
|
465
|
+
if (!tx) throw new Error(`Transaction ${txId} not found.`);
|
|
466
|
+
if (tx.status !== "pending") throw new Error(`Cannot approve transaction in '${tx.status}' state.`);
|
|
467
|
+
tx.status = "approved";
|
|
468
|
+
tx.mandate = mandate;
|
|
469
|
+
this.saveAll(transactions);
|
|
470
|
+
}
|
|
471
|
+
reject(txId, reason) {
|
|
472
|
+
const transactions = this.loadAll();
|
|
473
|
+
const tx = transactions.find((t) => t.id === txId);
|
|
474
|
+
if (!tx) throw new Error(`Transaction ${txId} not found.`);
|
|
475
|
+
if (tx.status !== "pending") throw new Error(`Cannot reject transaction in '${tx.status}' state.`);
|
|
476
|
+
tx.status = "rejected";
|
|
477
|
+
tx.rejectionReason = reason;
|
|
478
|
+
this.saveAll(transactions);
|
|
479
|
+
}
|
|
480
|
+
markExecuting(txId) {
|
|
481
|
+
const transactions = this.loadAll();
|
|
482
|
+
const tx = transactions.find((t) => t.id === txId);
|
|
483
|
+
if (!tx) throw new Error(`Transaction ${txId} not found.`);
|
|
484
|
+
if (tx.status !== "approved") throw new Error(`Cannot execute transaction in '${tx.status}' state.`);
|
|
485
|
+
tx.status = "executing";
|
|
486
|
+
this.saveAll(transactions);
|
|
487
|
+
}
|
|
488
|
+
markCompleted(txId, receipt) {
|
|
489
|
+
const transactions = this.loadAll();
|
|
490
|
+
const tx = transactions.find((t) => t.id === txId);
|
|
491
|
+
if (!tx) throw new Error(`Transaction ${txId} not found.`);
|
|
492
|
+
if (tx.status !== "executing") throw new Error(`Cannot complete transaction in '${tx.status}' state.`);
|
|
493
|
+
tx.status = "completed";
|
|
494
|
+
tx.receipt = receipt;
|
|
495
|
+
this.saveAll(transactions);
|
|
496
|
+
}
|
|
497
|
+
markFailed(txId, error) {
|
|
498
|
+
const transactions = this.loadAll();
|
|
499
|
+
const tx = transactions.find((t) => t.id === txId);
|
|
500
|
+
if (!tx) throw new Error(`Transaction ${txId} not found.`);
|
|
501
|
+
if (tx.status !== "executing") throw new Error(`Cannot fail transaction in '${tx.status}' state.`);
|
|
502
|
+
tx.status = "failed";
|
|
503
|
+
tx.error = error;
|
|
504
|
+
this.saveAll(transactions);
|
|
505
|
+
}
|
|
506
|
+
list() {
|
|
507
|
+
return this.loadAll();
|
|
508
|
+
}
|
|
509
|
+
getPending() {
|
|
510
|
+
return this.loadAll().filter((tx) => tx.status === "pending");
|
|
511
|
+
}
|
|
512
|
+
getHistory() {
|
|
513
|
+
return this.loadAll().filter((tx) => tx.status !== "pending");
|
|
514
|
+
}
|
|
515
|
+
};
|
|
516
|
+
|
|
517
|
+
// src/index.ts
|
|
518
|
+
init_poller();
|
|
519
|
+
|
|
520
|
+
// src/audit/logger.ts
|
|
521
|
+
init_esm_shims();
|
|
522
|
+
import { appendFileSync, readFileSync as readFileSync5, mkdirSync as mkdirSync5 } from "fs";
|
|
523
|
+
import { dirname as dirname5 } from "path";
|
|
524
|
+
var AuditLogger = class {
|
|
525
|
+
logPath;
|
|
526
|
+
constructor(logPath) {
|
|
527
|
+
this.logPath = logPath ?? getAuditPath();
|
|
528
|
+
}
|
|
529
|
+
log(action, details) {
|
|
530
|
+
const timestamp = (/* @__PURE__ */ new Date()).toISOString();
|
|
531
|
+
const detailsStr = JSON.stringify(details);
|
|
532
|
+
const entry = `${timestamp} ${action} ${detailsStr}
|
|
533
|
+
`;
|
|
534
|
+
mkdirSync5(dirname5(this.logPath), { recursive: true });
|
|
535
|
+
appendFileSync(this.logPath, entry, { mode: 384 });
|
|
536
|
+
}
|
|
537
|
+
getLog() {
|
|
538
|
+
try {
|
|
539
|
+
const data = readFileSync5(this.logPath, "utf8");
|
|
540
|
+
return data.trim().split("\n").filter(Boolean);
|
|
541
|
+
} catch {
|
|
542
|
+
return [];
|
|
543
|
+
}
|
|
544
|
+
}
|
|
545
|
+
};
|
|
546
|
+
|
|
547
|
+
// src/executor/executor.ts
|
|
548
|
+
init_esm_shims();
|
|
549
|
+
init_errors();
|
|
550
|
+
import { Stagehand } from "@browserbasehq/stagehand";
|
|
551
|
+
|
|
552
|
+
// src/executor/placeholder.ts
|
|
553
|
+
init_esm_shims();
|
|
554
|
+
var PLACEHOLDER_MAP = {
|
|
555
|
+
card_number: "{{card_number}}",
|
|
556
|
+
cardholder_name: "{{cardholder_name}}",
|
|
557
|
+
card_expiry: "{{card_expiry}}",
|
|
558
|
+
card_cvv: "{{card_cvv}}",
|
|
559
|
+
billing_street: "{{billing_street}}",
|
|
560
|
+
billing_city: "{{billing_city}}",
|
|
561
|
+
billing_state: "{{billing_state}}",
|
|
562
|
+
billing_zip: "{{billing_zip}}",
|
|
563
|
+
billing_country: "{{billing_country}}",
|
|
564
|
+
shipping_street: "{{shipping_street}}",
|
|
565
|
+
shipping_city: "{{shipping_city}}",
|
|
566
|
+
shipping_state: "{{shipping_state}}",
|
|
567
|
+
shipping_zip: "{{shipping_zip}}",
|
|
568
|
+
shipping_country: "{{shipping_country}}",
|
|
569
|
+
email: "{{email}}",
|
|
570
|
+
phone: "{{phone}}"
|
|
571
|
+
};
|
|
572
|
+
function getPlaceholderVariables() {
|
|
573
|
+
return {
|
|
574
|
+
card_number: "%card_number%",
|
|
575
|
+
cardholder_name: "%cardholder_name%",
|
|
576
|
+
card_expiry: "%card_expiry%",
|
|
577
|
+
card_cvv: "%card_cvv%",
|
|
578
|
+
billing_street: "%billing_street%",
|
|
579
|
+
billing_city: "%billing_city%",
|
|
580
|
+
billing_state: "%billing_state%",
|
|
581
|
+
billing_zip: "%billing_zip%",
|
|
582
|
+
billing_country: "%billing_country%",
|
|
583
|
+
shipping_street: "%shipping_street%",
|
|
584
|
+
shipping_city: "%shipping_city%",
|
|
585
|
+
shipping_state: "%shipping_state%",
|
|
586
|
+
shipping_zip: "%shipping_zip%",
|
|
587
|
+
shipping_country: "%shipping_country%",
|
|
588
|
+
email: "%email%",
|
|
589
|
+
phone: "%phone%"
|
|
590
|
+
};
|
|
591
|
+
}
|
|
592
|
+
function credentialsToSwapMap(creds) {
|
|
593
|
+
return {
|
|
594
|
+
[PLACEHOLDER_MAP.card_number]: creds.card.number,
|
|
595
|
+
[PLACEHOLDER_MAP.cardholder_name]: creds.name,
|
|
596
|
+
[PLACEHOLDER_MAP.card_expiry]: creds.card.expiry,
|
|
597
|
+
[PLACEHOLDER_MAP.card_cvv]: creds.card.cvv,
|
|
598
|
+
[PLACEHOLDER_MAP.billing_street]: creds.billingAddress.street,
|
|
599
|
+
[PLACEHOLDER_MAP.billing_city]: creds.billingAddress.city,
|
|
600
|
+
[PLACEHOLDER_MAP.billing_state]: creds.billingAddress.state,
|
|
601
|
+
[PLACEHOLDER_MAP.billing_zip]: creds.billingAddress.zip,
|
|
602
|
+
[PLACEHOLDER_MAP.billing_country]: creds.billingAddress.country,
|
|
603
|
+
[PLACEHOLDER_MAP.shipping_street]: creds.shippingAddress.street,
|
|
604
|
+
[PLACEHOLDER_MAP.shipping_city]: creds.shippingAddress.city,
|
|
605
|
+
[PLACEHOLDER_MAP.shipping_state]: creds.shippingAddress.state,
|
|
606
|
+
[PLACEHOLDER_MAP.shipping_zip]: creds.shippingAddress.zip,
|
|
607
|
+
[PLACEHOLDER_MAP.shipping_country]: creds.shippingAddress.country,
|
|
608
|
+
[PLACEHOLDER_MAP.email]: creds.email,
|
|
609
|
+
[PLACEHOLDER_MAP.phone]: creds.phone
|
|
610
|
+
};
|
|
611
|
+
}
|
|
612
|
+
|
|
613
|
+
// src/executor/executor.ts
|
|
614
|
+
var PurchaseExecutor = class {
|
|
615
|
+
config;
|
|
616
|
+
stagehand = null;
|
|
617
|
+
constructor(config) {
|
|
618
|
+
this.config = {
|
|
619
|
+
browserbaseApiKey: config?.browserbaseApiKey ?? process.env.BROWSERBASE_API_KEY,
|
|
620
|
+
browserbaseProjectId: config?.browserbaseProjectId ?? process.env.BROWSERBASE_PROJECT_ID,
|
|
621
|
+
modelApiKey: config?.modelApiKey ?? process.env.ANTHROPIC_API_KEY
|
|
622
|
+
};
|
|
623
|
+
}
|
|
624
|
+
createStagehand() {
|
|
625
|
+
return new Stagehand({
|
|
626
|
+
env: "BROWSERBASE",
|
|
627
|
+
apiKey: this.config.browserbaseApiKey,
|
|
628
|
+
projectId: this.config.browserbaseProjectId,
|
|
629
|
+
model: this.config.modelApiKey ? { modelName: "claude-3-7-sonnet-latest", apiKey: this.config.modelApiKey } : void 0,
|
|
630
|
+
browserbaseSessionCreateParams: {
|
|
631
|
+
browserSettings: {
|
|
632
|
+
recordSession: false
|
|
633
|
+
}
|
|
634
|
+
}
|
|
635
|
+
});
|
|
636
|
+
}
|
|
637
|
+
/**
|
|
638
|
+
* Phase 1: Open browser, navigate to URL, extract price and product info.
|
|
639
|
+
* Keeps the session alive for fillAndComplete().
|
|
640
|
+
*/
|
|
641
|
+
async openAndDiscover(url, instructions) {
|
|
642
|
+
this.stagehand = this.createStagehand();
|
|
643
|
+
await this.stagehand.init();
|
|
644
|
+
const page = this.stagehand.context.activePage();
|
|
645
|
+
await page.goto(url);
|
|
646
|
+
const addToCartInstructions = instructions ? `On this product page: ${instructions}. Then add the item to the cart.` : "Add this product to the cart.";
|
|
647
|
+
await this.stagehand.act(addToCartInstructions);
|
|
648
|
+
const extracted = await this.stagehand.extract(
|
|
649
|
+
'Extract the product name and the price (as a number without $ sign) from the page or cart. Return JSON: { "price": <number>, "productName": "<string>" }'
|
|
650
|
+
);
|
|
651
|
+
const price = parseFloat(extracted?.price ?? extracted?.extraction?.price ?? "0");
|
|
652
|
+
const productName = extracted?.productName ?? extracted?.extraction?.productName ?? "Unknown Product";
|
|
653
|
+
return { price, productName };
|
|
654
|
+
}
|
|
655
|
+
/**
|
|
656
|
+
* Phase 2: Proceed to checkout, fill forms, swap credentials, and submit.
|
|
657
|
+
* Must be called after openAndDiscover().
|
|
658
|
+
*/
|
|
659
|
+
async fillAndComplete(credentials) {
|
|
660
|
+
if (!this.stagehand) {
|
|
661
|
+
throw new CheckoutFailedError("No active session. Call openAndDiscover() first.");
|
|
662
|
+
}
|
|
663
|
+
try {
|
|
664
|
+
await this.stagehand.act("Proceed to checkout from the cart.");
|
|
665
|
+
const variables = getPlaceholderVariables();
|
|
666
|
+
await this.stagehand.act(
|
|
667
|
+
`Fill in the checkout form with these values:
|
|
668
|
+
Name: ${variables.cardholder_name}
|
|
669
|
+
Card Number: ${variables.card_number}
|
|
670
|
+
Expiry: ${variables.card_expiry}
|
|
671
|
+
CVV: ${variables.card_cvv}
|
|
672
|
+
Email: ${variables.email}
|
|
673
|
+
Phone: ${variables.phone}
|
|
674
|
+
Billing Street: ${variables.billing_street}
|
|
675
|
+
Billing City: ${variables.billing_city}
|
|
676
|
+
Billing State: ${variables.billing_state}
|
|
677
|
+
Billing ZIP: ${variables.billing_zip}
|
|
678
|
+
Billing Country: ${variables.billing_country}
|
|
679
|
+
Shipping Street: ${variables.shipping_street}
|
|
680
|
+
Shipping City: ${variables.shipping_city}
|
|
681
|
+
Shipping State: ${variables.shipping_state}
|
|
682
|
+
Shipping ZIP: ${variables.shipping_zip}
|
|
683
|
+
Shipping Country: ${variables.shipping_country}`,
|
|
684
|
+
{ variables }
|
|
685
|
+
);
|
|
686
|
+
const swapMap = credentialsToSwapMap(credentials);
|
|
687
|
+
const page = this.stagehand.context.activePage();
|
|
688
|
+
await page.evaluate((map) => {
|
|
689
|
+
const inputs = document.querySelectorAll("input, textarea, select");
|
|
690
|
+
for (const input of inputs) {
|
|
691
|
+
const el = input;
|
|
692
|
+
for (const [placeholder, value] of Object.entries(map)) {
|
|
693
|
+
if (el.value === placeholder) {
|
|
694
|
+
el.value = value;
|
|
695
|
+
el.dispatchEvent(new Event("input", { bubbles: true }));
|
|
696
|
+
el.dispatchEvent(new Event("change", { bubbles: true }));
|
|
697
|
+
}
|
|
698
|
+
}
|
|
699
|
+
}
|
|
700
|
+
const submitBtn = document.querySelector('button[type="submit"], input[type="submit"]');
|
|
701
|
+
if (submitBtn) submitBtn.click();
|
|
702
|
+
}, swapMap);
|
|
703
|
+
await page.waitForTimeout(5e3);
|
|
704
|
+
const result = await this.stagehand.extract(
|
|
705
|
+
"Extract the order confirmation number or ID from the page"
|
|
706
|
+
);
|
|
707
|
+
const confirmationId = result?.extraction;
|
|
708
|
+
if (!confirmationId || confirmationId === "null" || confirmationId === "UNKNOWN") {
|
|
709
|
+
throw new CheckoutFailedError("No order confirmation found. Checkout may not have completed.");
|
|
710
|
+
}
|
|
711
|
+
return {
|
|
712
|
+
success: true,
|
|
713
|
+
confirmationId
|
|
714
|
+
};
|
|
715
|
+
} catch (err) {
|
|
716
|
+
if (err instanceof CheckoutFailedError) throw err;
|
|
717
|
+
const message = err instanceof Error ? err.message : "Unknown checkout error";
|
|
718
|
+
throw new CheckoutFailedError(message);
|
|
719
|
+
}
|
|
720
|
+
}
|
|
721
|
+
/**
|
|
722
|
+
* Convenience: single-shot execute (navigate + checkout in one call).
|
|
723
|
+
* Used by AgentPay facade when amount is already known.
|
|
724
|
+
*/
|
|
725
|
+
async execute(tx, credentials) {
|
|
726
|
+
this.stagehand = this.createStagehand();
|
|
727
|
+
try {
|
|
728
|
+
await this.stagehand.init();
|
|
729
|
+
const page = this.stagehand.context.activePage();
|
|
730
|
+
await page.goto(tx.url);
|
|
731
|
+
const variables = getPlaceholderVariables();
|
|
732
|
+
await this.stagehand.act(
|
|
733
|
+
`Find the product and proceed to checkout. Fill in the checkout form with these values:
|
|
734
|
+
Name: ${variables.cardholder_name}
|
|
735
|
+
Card Number: ${variables.card_number}
|
|
736
|
+
Expiry: ${variables.card_expiry}
|
|
737
|
+
CVV: ${variables.card_cvv}
|
|
738
|
+
Email: ${variables.email}
|
|
739
|
+
Phone: ${variables.phone}
|
|
740
|
+
Billing Street: ${variables.billing_street}
|
|
741
|
+
Billing City: ${variables.billing_city}
|
|
742
|
+
Billing State: ${variables.billing_state}
|
|
743
|
+
Billing ZIP: ${variables.billing_zip}
|
|
744
|
+
Billing Country: ${variables.billing_country}
|
|
745
|
+
Shipping Street: ${variables.shipping_street}
|
|
746
|
+
Shipping City: ${variables.shipping_city}
|
|
747
|
+
Shipping State: ${variables.shipping_state}
|
|
748
|
+
Shipping ZIP: ${variables.shipping_zip}
|
|
749
|
+
Shipping Country: ${variables.shipping_country}`,
|
|
750
|
+
{ variables }
|
|
751
|
+
);
|
|
752
|
+
const swapMap = credentialsToSwapMap(credentials);
|
|
753
|
+
await page.evaluate((map) => {
|
|
754
|
+
const inputs = document.querySelectorAll("input, textarea, select");
|
|
755
|
+
for (const input of inputs) {
|
|
756
|
+
const el = input;
|
|
757
|
+
for (const [placeholder, value] of Object.entries(map)) {
|
|
758
|
+
if (el.value === placeholder) {
|
|
759
|
+
el.value = value;
|
|
760
|
+
el.dispatchEvent(new Event("input", { bubbles: true }));
|
|
761
|
+
el.dispatchEvent(new Event("change", { bubbles: true }));
|
|
762
|
+
}
|
|
763
|
+
}
|
|
764
|
+
}
|
|
765
|
+
const submitBtn = document.querySelector('button[type="submit"], input[type="submit"]');
|
|
766
|
+
if (submitBtn) submitBtn.click();
|
|
767
|
+
}, swapMap);
|
|
768
|
+
await page.waitForTimeout(5e3);
|
|
769
|
+
const result = await this.stagehand.extract(
|
|
770
|
+
"Extract the order confirmation number or ID from the page"
|
|
771
|
+
);
|
|
772
|
+
const confirmationId = result?.extraction;
|
|
773
|
+
if (!confirmationId || confirmationId === "null" || confirmationId === "UNKNOWN") {
|
|
774
|
+
throw new CheckoutFailedError("No order confirmation found. Checkout may not have completed.");
|
|
775
|
+
}
|
|
776
|
+
return {
|
|
777
|
+
success: true,
|
|
778
|
+
confirmationId
|
|
779
|
+
};
|
|
780
|
+
} catch (err) {
|
|
781
|
+
if (err instanceof CheckoutFailedError) throw err;
|
|
782
|
+
const message = err instanceof Error ? err.message : "Unknown checkout error";
|
|
783
|
+
throw new CheckoutFailedError(message);
|
|
784
|
+
} finally {
|
|
785
|
+
await this.close();
|
|
786
|
+
}
|
|
787
|
+
}
|
|
788
|
+
async close() {
|
|
789
|
+
try {
|
|
790
|
+
if (this.stagehand) {
|
|
791
|
+
await this.stagehand.close();
|
|
792
|
+
this.stagehand = null;
|
|
793
|
+
}
|
|
794
|
+
} catch {
|
|
795
|
+
}
|
|
796
|
+
}
|
|
797
|
+
};
|
|
798
|
+
|
|
799
|
+
// src/agentpay.ts
|
|
800
|
+
init_esm_shims();
|
|
801
|
+
import { join as join2 } from "path";
|
|
802
|
+
init_errors();
|
|
803
|
+
var AgentPay = class {
|
|
804
|
+
home;
|
|
805
|
+
passphrase;
|
|
806
|
+
budgetManager;
|
|
807
|
+
txManager;
|
|
808
|
+
auditLogger;
|
|
809
|
+
executor;
|
|
810
|
+
constructor(options) {
|
|
811
|
+
this.home = options?.home ?? getHomePath();
|
|
812
|
+
this.passphrase = options?.passphrase;
|
|
813
|
+
this.budgetManager = new BudgetManager(join2(this.home, "wallet.json"));
|
|
814
|
+
this.txManager = new TransactionManager(join2(this.home, "transactions.json"));
|
|
815
|
+
this.auditLogger = new AuditLogger(join2(this.home, "audit.log"));
|
|
816
|
+
this.executor = new PurchaseExecutor(options?.executor);
|
|
817
|
+
}
|
|
818
|
+
get wallet() {
|
|
819
|
+
const bm = this.budgetManager;
|
|
820
|
+
return {
|
|
821
|
+
getBalance: () => bm.getBalance(),
|
|
822
|
+
getHistory: () => this.txManager.getHistory(),
|
|
823
|
+
getLimits: () => {
|
|
824
|
+
const w = bm.getBalance();
|
|
825
|
+
return { budget: w.budget, limitPerTx: w.limitPerTx, remaining: w.balance };
|
|
826
|
+
},
|
|
827
|
+
generateFundingQR: async (options) => {
|
|
828
|
+
const QRCode = await import("qrcode");
|
|
829
|
+
const params = new URLSearchParams();
|
|
830
|
+
if (options?.suggestedBudget) params.set("budget", String(options.suggestedBudget));
|
|
831
|
+
if (options?.message) params.set("msg", options.message);
|
|
832
|
+
const baseUrl = process.env.AGENTPAY_WEB_URL ?? "http://localhost:3000";
|
|
833
|
+
const url = `${baseUrl}/setup${params.toString() ? `?${params.toString()}` : ""}`;
|
|
834
|
+
const qrDataUrl = await QRCode.toDataURL(url);
|
|
835
|
+
return { url, qrDataUrl };
|
|
836
|
+
}
|
|
837
|
+
};
|
|
838
|
+
}
|
|
839
|
+
get transactions() {
|
|
840
|
+
return {
|
|
841
|
+
propose: (options) => {
|
|
842
|
+
this.budgetManager.checkProposal(options.amount);
|
|
843
|
+
const tx = this.txManager.propose(options);
|
|
844
|
+
this.auditLogger.log("PROPOSE", { txId: tx.id, merchant: tx.merchant, amount: tx.amount });
|
|
845
|
+
return tx;
|
|
846
|
+
},
|
|
847
|
+
get: (txId) => this.txManager.get(txId),
|
|
848
|
+
waitForApproval: async (txId, options) => {
|
|
849
|
+
const { waitForApproval: waitForApproval2 } = await Promise.resolve().then(() => (init_poller(), poller_exports));
|
|
850
|
+
return waitForApproval2(txId, this.txManager, options);
|
|
851
|
+
},
|
|
852
|
+
execute: async (txId) => {
|
|
853
|
+
const tx = this.txManager.get(txId);
|
|
854
|
+
if (!tx) throw new Error(`Transaction ${txId} not found.`);
|
|
855
|
+
if (tx.status === "completed" || tx.status === "failed") {
|
|
856
|
+
throw new AlreadyExecutedError(txId);
|
|
857
|
+
}
|
|
858
|
+
if (tx.status !== "approved") {
|
|
859
|
+
throw new NotApprovedError(txId);
|
|
860
|
+
}
|
|
861
|
+
if (!tx.mandate) {
|
|
862
|
+
throw new InvalidMandateError("No mandate found on approved transaction.");
|
|
863
|
+
}
|
|
864
|
+
const txDetails = {
|
|
865
|
+
txId: tx.id,
|
|
866
|
+
merchant: tx.merchant,
|
|
867
|
+
amount: tx.amount,
|
|
868
|
+
description: tx.description,
|
|
869
|
+
timestamp: tx.createdAt
|
|
870
|
+
};
|
|
871
|
+
if (!verifyMandate(tx.mandate, txDetails)) {
|
|
872
|
+
throw new InvalidMandateError();
|
|
873
|
+
}
|
|
874
|
+
this.budgetManager.checkProposal(tx.amount);
|
|
875
|
+
this.txManager.markExecuting(txId);
|
|
876
|
+
this.auditLogger.log("EXECUTE", { txId, browserbaseSessionStarted: true });
|
|
877
|
+
try {
|
|
878
|
+
if (!this.passphrase) {
|
|
879
|
+
throw new Error("Passphrase required for execution. Pass it to AgentPay constructor.");
|
|
880
|
+
}
|
|
881
|
+
const vaultPath = join2(this.home, "credentials.enc");
|
|
882
|
+
const vault = loadVault(vaultPath);
|
|
883
|
+
const credentials = decrypt(vault, this.passphrase);
|
|
884
|
+
const result = await this.executor.execute(tx, credentials);
|
|
885
|
+
const receipt = {
|
|
886
|
+
id: `rcpt_${txId.replace("tx_", "")}`,
|
|
887
|
+
merchant: tx.merchant,
|
|
888
|
+
amount: tx.amount,
|
|
889
|
+
confirmationId: result.confirmationId ?? "UNKNOWN",
|
|
890
|
+
completedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
891
|
+
};
|
|
892
|
+
this.txManager.markCompleted(txId, receipt);
|
|
893
|
+
this.budgetManager.deductBalance(tx.amount);
|
|
894
|
+
this.auditLogger.log("COMPLETE", { txId, confirmationId: receipt.confirmationId });
|
|
895
|
+
return receipt;
|
|
896
|
+
} catch (err) {
|
|
897
|
+
this.txManager.markFailed(txId, err instanceof Error ? err.message : "Unknown error");
|
|
898
|
+
this.auditLogger.log("FAILED", { txId, error: err instanceof Error ? err.message : "Unknown" });
|
|
899
|
+
throw err;
|
|
900
|
+
}
|
|
901
|
+
},
|
|
902
|
+
getReceipt: (txId) => {
|
|
903
|
+
const tx = this.txManager.get(txId);
|
|
904
|
+
return tx?.receipt;
|
|
905
|
+
}
|
|
906
|
+
};
|
|
907
|
+
}
|
|
908
|
+
get audit() {
|
|
909
|
+
return { getLog: () => this.auditLogger.getLog() };
|
|
910
|
+
}
|
|
911
|
+
status() {
|
|
912
|
+
try {
|
|
913
|
+
const wallet = this.budgetManager.getBalance();
|
|
914
|
+
const pending = this.txManager.getPending();
|
|
915
|
+
const recent = this.txManager.list().slice(-5);
|
|
916
|
+
return {
|
|
917
|
+
balance: wallet.balance,
|
|
918
|
+
budget: wallet.budget,
|
|
919
|
+
limitPerTx: wallet.limitPerTx,
|
|
920
|
+
pending,
|
|
921
|
+
recent,
|
|
922
|
+
isSetup: true
|
|
923
|
+
};
|
|
924
|
+
} catch {
|
|
925
|
+
return {
|
|
926
|
+
balance: 0,
|
|
927
|
+
budget: 0,
|
|
928
|
+
limitPerTx: 0,
|
|
929
|
+
pending: [],
|
|
930
|
+
recent: [],
|
|
931
|
+
isSetup: false
|
|
932
|
+
};
|
|
933
|
+
}
|
|
934
|
+
}
|
|
935
|
+
};
|
|
936
|
+
|
|
937
|
+
// src/index.ts
|
|
938
|
+
var VERSION = "0.1.0";
|
|
939
|
+
export {
|
|
940
|
+
AgentPay,
|
|
941
|
+
AlreadyExecutedError,
|
|
942
|
+
AuditLogger,
|
|
943
|
+
BudgetManager,
|
|
944
|
+
CheckoutFailedError,
|
|
945
|
+
DecryptError,
|
|
946
|
+
ExceedsTxLimitError,
|
|
947
|
+
InsufficientBalanceError,
|
|
948
|
+
InvalidMandateError,
|
|
949
|
+
NotApprovedError,
|
|
950
|
+
NotSetupError,
|
|
951
|
+
PLACEHOLDER_MAP,
|
|
952
|
+
PurchaseExecutor,
|
|
953
|
+
TimeoutError,
|
|
954
|
+
TransactionManager,
|
|
955
|
+
VERSION,
|
|
956
|
+
createMandate,
|
|
957
|
+
credentialsToSwapMap,
|
|
958
|
+
decrypt,
|
|
959
|
+
encrypt,
|
|
960
|
+
formatCurrency,
|
|
961
|
+
formatStatus,
|
|
962
|
+
formatTable,
|
|
963
|
+
formatTimestamp,
|
|
964
|
+
generateKeyPair,
|
|
965
|
+
generateTxId,
|
|
966
|
+
getAuditPath,
|
|
967
|
+
getCredentialsPath,
|
|
968
|
+
getHomePath,
|
|
969
|
+
getKeysPath,
|
|
970
|
+
getPlaceholderVariables,
|
|
971
|
+
getTransactionsPath,
|
|
972
|
+
getWalletPath,
|
|
973
|
+
loadPrivateKey,
|
|
974
|
+
loadPublicKey,
|
|
975
|
+
loadVault,
|
|
976
|
+
saveKeyPair,
|
|
977
|
+
saveVault,
|
|
978
|
+
verifyMandate,
|
|
979
|
+
waitForApproval
|
|
980
|
+
};
|
|
981
|
+
//# sourceMappingURL=index.js.map
|