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