@rozoai/checkout 0.1.15 → 0.1.17

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.
@@ -3441,6 +3441,9 @@ function carry(digits) {
3441
3441
  }
3442
3442
  return `1${out.join("")}`;
3443
3443
  }
3444
+ function fromGwei(gwei, unit = "wei") {
3445
+ return from(gwei, exponents.gwei - exponents[unit]);
3446
+ }
3444
3447
  var exponents, InvalidDecimalNumberError, InvalidDecimalsError;
3445
3448
  var init_Value = __esm({
3446
3449
  "node_modules/viem/_esm/utils/unit/Value.js"() {
@@ -10006,7 +10009,20 @@ function redact(text) {
10006
10009
  );
10007
10010
  return s;
10008
10011
  }
10012
+ var PublicHash = class {
10013
+ constructor(value) {
10014
+ this.value = value;
10015
+ }
10016
+ toJSON() {
10017
+ return this.value;
10018
+ }
10019
+ };
10020
+ var TX_HASH_VALUE = /^(0x[0-9a-fA-F]{64}|[1-9A-HJ-NP-Za-km-z]{86,88})$/;
10021
+ function publicHash(value) {
10022
+ return typeof value === "string" && TX_HASH_VALUE.test(value) ? new PublicHash(value) : value;
10023
+ }
10009
10024
  function redactDeep(value) {
10025
+ if (value instanceof PublicHash) return value.value;
10010
10026
  if (typeof value === "string") return redact(value);
10011
10027
  if (Array.isArray(value)) return value.map(redactDeep);
10012
10028
  if (value && typeof value === "object") {
@@ -18315,6 +18331,12 @@ function formatUnits(value, decimals) {
18315
18331
  return format(value, decimals);
18316
18332
  }
18317
18333
 
18334
+ // node_modules/viem/_esm/utils/unit/parseGwei.js
18335
+ init_Value();
18336
+ function parseGwei(ether, unit = "wei") {
18337
+ return fromGwei(ether, unit);
18338
+ }
18339
+
18318
18340
  // node_modules/viem/_esm/utils/unit/parseUnits.js
18319
18341
  init_Value();
18320
18342
  function parseUnits(value, decimals) {
@@ -18458,7 +18480,7 @@ init_createBatchScheduler();
18458
18480
  init_stringify();
18459
18481
  async function multicall(client, parameters) {
18460
18482
  const { account, authorizationList, allowFailure = true, blockHash, blockNumber, blockOverrides, blockTag, requireCanonical, stateOverride } = parameters;
18461
- const contracts = parameters.contracts;
18483
+ const contracts2 = parameters.contracts;
18462
18484
  const batch = typeof client.batch?.multicall === "object" ? client.batch.multicall : {};
18463
18485
  const batchSize = parameters.batchSize ?? batch.batchSize ?? 1024;
18464
18486
  const deployless = parameters.deployless ?? batch.deployless ?? false;
@@ -18479,8 +18501,8 @@ async function multicall(client, parameters) {
18479
18501
  const chunkedCalls = [[]];
18480
18502
  let currentChunk = 0;
18481
18503
  let currentChunkSize = 0;
18482
- for (let i = 0; i < contracts.length; i++) {
18483
- const { abi: abi2, address, args, functionName } = contracts[i];
18504
+ for (let i = 0; i < contracts2.length; i++) {
18505
+ const { abi: abi2, address, args, functionName } = contracts2[i];
18484
18506
  try {
18485
18507
  const callData = encodeFunctionData({ abi: abi2, args, functionName });
18486
18508
  currentChunkSize += (callData.length - 2) / 2;
@@ -18574,7 +18596,7 @@ async function multicall(client, parameters) {
18574
18596
  for (let j = 0; j < aggregate3Result.length; j++) {
18575
18597
  const { returnData, success } = aggregate3Result[j];
18576
18598
  const { callData } = batches[i][j];
18577
- const { abi: abi2, address, functionName, args } = contracts[results.length];
18599
+ const { abi: abi2, address, functionName, args } = contracts2[results.length];
18578
18600
  try {
18579
18601
  if (callData === "0x")
18580
18602
  throw new AbiDecodingZeroDataError();
@@ -18601,7 +18623,7 @@ async function multicall(client, parameters) {
18601
18623
  }
18602
18624
  }
18603
18625
  }
18604
- if (results.length !== contracts.length)
18626
+ if (results.length !== contracts2.length)
18605
18627
  throw new BaseError2("multicall results mismatch");
18606
18628
  return results;
18607
18629
  }
@@ -21895,6 +21917,199 @@ function getJson(url, opts) {
21895
21917
  return request("GET", url, opts);
21896
21918
  }
21897
21919
 
21920
+ // scripts/src/lib/state.mjs
21921
+ import fs4 from "node:fs";
21922
+ import path4 from "node:path";
21923
+ import os3 from "node:os";
21924
+ import crypto5 from "node:crypto";
21925
+ var LOCK_STALE_MS = 6e4;
21926
+ var LOCK_WAIT_MS = 1e4;
21927
+ var LOCK_POLL_MS = 25;
21928
+ function lockPath() {
21929
+ return path4.join(stateRoot(), ".send.lock");
21930
+ }
21931
+ function sleepSync(ms) {
21932
+ Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, ms);
21933
+ }
21934
+ function tryAcquire(file) {
21935
+ try {
21936
+ const fd = fs4.openSync(file, "wx", 384);
21937
+ fs4.writeFileSync(fd, JSON.stringify({ pid: process.pid, at: (/* @__PURE__ */ new Date()).toISOString() }));
21938
+ fs4.closeSync(fd);
21939
+ return true;
21940
+ } catch (err) {
21941
+ if (err.code !== "EEXIST") throw err;
21942
+ return false;
21943
+ }
21944
+ }
21945
+ function withLock(fn) {
21946
+ const file = lockPath();
21947
+ fs4.mkdirSync(path4.dirname(file), { recursive: true, mode: 448 });
21948
+ const deadline = Date.now() + LOCK_WAIT_MS;
21949
+ for (; ; ) {
21950
+ if (tryAcquire(file)) break;
21951
+ try {
21952
+ const age = Date.now() - fs4.statSync(file).mtimeMs;
21953
+ if (age > LOCK_STALE_MS) {
21954
+ const stolen = `${file}.stale.${crypto5.randomBytes(4).toString("hex")}`;
21955
+ try {
21956
+ fs4.renameSync(file, stolen);
21957
+ fs4.unlinkSync(stolen);
21958
+ } catch {
21959
+ }
21960
+ continue;
21961
+ }
21962
+ } catch (err) {
21963
+ if (err.code !== "ENOENT") throw err;
21964
+ continue;
21965
+ }
21966
+ if (Date.now() > deadline) {
21967
+ throw new SkillError(
21968
+ "LOCK_TIMEOUT",
21969
+ "Another rozo-checkout process is holding the send lock. Refusing to proceed rather than risk a concurrent second send."
21970
+ );
21971
+ }
21972
+ sleepSync(LOCK_POLL_MS);
21973
+ }
21974
+ try {
21975
+ return fn();
21976
+ } finally {
21977
+ try {
21978
+ fs4.unlinkSync(file);
21979
+ } catch {
21980
+ }
21981
+ }
21982
+ }
21983
+ function stateRoot() {
21984
+ return process.env.ROZO_CHECKOUT_STATE_DIR || path4.join(os3.homedir(), ".rozo-checkout", "state");
21985
+ }
21986
+ function statePath(rozoPaymentId) {
21987
+ if (!/^[A-Za-z0-9-]{8,64}$/.test(String(rozoPaymentId || ""))) {
21988
+ throw new SkillError("BAD_ROZO_PAYMENT_ID", "Refusing to build a state path from that id.");
21989
+ }
21990
+ return path4.join(stateRoot(), `${rozoPaymentId}.json`);
21991
+ }
21992
+ function writeAtomic(file, data) {
21993
+ const dir = path4.dirname(file);
21994
+ fs4.mkdirSync(dir, { recursive: true, mode: 448 });
21995
+ const tmp = path4.join(dir, `.${path4.basename(file)}.${crypto5.randomBytes(6).toString("hex")}.tmp`);
21996
+ const fd = fs4.openSync(tmp, "wx", 384);
21997
+ try {
21998
+ fs4.writeFileSync(fd, JSON.stringify(data, null, 2) + "\n", "utf8");
21999
+ fs4.fsyncSync(fd);
22000
+ } finally {
22001
+ fs4.closeSync(fd);
22002
+ }
22003
+ fs4.renameSync(tmp, file);
22004
+ }
22005
+ function readState(rozoPaymentId) {
22006
+ const file = statePath(rozoPaymentId);
22007
+ let raw;
22008
+ try {
22009
+ raw = fs4.readFileSync(file, "utf8");
22010
+ } catch (err) {
22011
+ if (err.code === "ENOENT") return null;
22012
+ throw new SkillError("STATE_UNREADABLE", `Cannot read local state: ${err.code}`);
22013
+ }
22014
+ try {
22015
+ return JSON.parse(raw);
22016
+ } catch {
22017
+ throw new SkillError(
22018
+ "STATE_CORRUPT",
22019
+ "The local state file for this order is corrupt. Refusing to act; inspect it manually."
22020
+ );
22021
+ }
22022
+ }
22023
+ function depositDigest(source) {
22024
+ const canonical = JSON.stringify({
22025
+ chainId: String(source?.chainId ?? ""),
22026
+ tokenSymbol: String(source?.tokenSymbol ?? "").toUpperCase(),
22027
+ tokenAddress: String(source?.tokenAddress ?? ""),
22028
+ receiverAddress: String(source?.receiverAddress ?? ""),
22029
+ receiverMemo: source?.receiverMemo ?? null,
22030
+ amount: String(source?.amount ?? ""),
22031
+ amountUnit: source?.amountUnit ?? null,
22032
+ lnInvoice: source?.lnInvoice ?? null
22033
+ });
22034
+ return crypto5.createHash("sha256").update(canonical, "utf8").digest("hex");
22035
+ }
22036
+ function claimSend(rozoPaymentId, intent, { skipCaps = false } = {}) {
22037
+ return withLock(() => {
22038
+ const state = readState(rozoPaymentId);
22039
+ if (!state) {
22040
+ throw new SkillError(
22041
+ "NO_ORDER_STATE",
22042
+ "No local record for this order. Run create-order.js in this same session first."
22043
+ );
22044
+ }
22045
+ if (state.send) {
22046
+ throw new SkillError(
22047
+ "ALREADY_SENT",
22048
+ `A send was already recorded for this order at ${state.send.claimedAt} (status: ${state.send.status}). Refusing to send twice. Check the backend for the pay-in before doing anything else.`,
22049
+ { send: state.send }
22050
+ );
22051
+ }
22052
+ const caps = skipCaps ? null : assertPaymentLimit(state.invoiceAmount);
22053
+ const next = {
22054
+ ...state,
22055
+ updatedAt: (/* @__PURE__ */ new Date()).toISOString(),
22056
+ send: {
22057
+ status: "claimed",
22058
+ claimedAt: (/* @__PURE__ */ new Date()).toISOString(),
22059
+ chainId: intent.chainId,
22060
+ tokenSymbol: intent.tokenSymbol,
22061
+ from: intent.from,
22062
+ to: intent.to,
22063
+ amountAtomic: intent.amountAtomic,
22064
+ memo: intent.memo ?? null,
22065
+ nonceBefore: intent.nonceBefore ?? null,
22066
+ expectedTxHash: intent.expectedTxHash ?? null,
22067
+ txHash: null
22068
+ }
22069
+ };
22070
+ writeAtomic(statePath(rozoPaymentId), next);
22071
+ return { state: next, caps };
22072
+ });
22073
+ }
22074
+ function recordSendResult(rozoPaymentId, { status, txHash = null, note = null }) {
22075
+ return withLock(() => {
22076
+ const state = readState(rozoPaymentId);
22077
+ if (!state || !state.send) {
22078
+ throw new SkillError("NO_SEND_CLAIM", "No send claim to update for this order.");
22079
+ }
22080
+ const next = {
22081
+ ...state,
22082
+ updatedAt: (/* @__PURE__ */ new Date()).toISOString(),
22083
+ send: {
22084
+ ...state.send,
22085
+ status,
22086
+ txHash: txHash ?? state.send.txHash,
22087
+ note,
22088
+ resolvedAt: (/* @__PURE__ */ new Date()).toISOString()
22089
+ }
22090
+ };
22091
+ writeAtomic(statePath(rozoPaymentId), next);
22092
+ return next;
22093
+ });
22094
+ }
22095
+ var MAX_PAYMENT_USD = 1100;
22096
+ function assertPaymentLimit(invoiceUsd) {
22097
+ if (invoiceUsd === null || invoiceUsd === void 0 || String(invoiceUsd).trim() === "") {
22098
+ throw new SkillError("BAD_INVOICE_AMOUNT", "Cannot evaluate the payment limit without a USD amount.");
22099
+ }
22100
+ const usd = Number(invoiceUsd);
22101
+ if (!Number.isFinite(usd) || usd < 0) {
22102
+ throw new SkillError("BAD_INVOICE_AMOUNT", "Cannot evaluate the payment limit without a USD amount.");
22103
+ }
22104
+ if (usd > MAX_PAYMENT_USD) {
22105
+ throw new SkillError(
22106
+ "CAP_PER_TX",
22107
+ `$${usd} is above the $${MAX_PAYMENT_USD} per-payment limit for automated sending. This limit has no override. Pay this invoice from a wallet you control instead \u2014 that path needs no key and has no limit.`
22108
+ );
22109
+ }
22110
+ return { usd, limitUsd: MAX_PAYMENT_USD };
22111
+ }
22112
+
21898
22113
  // scripts/src/lib/api.mjs
21899
22114
  var PKG_VERSION = (() => {
21900
22115
  const requireFrom = createRequire(import.meta.url);
@@ -22244,9 +22459,9 @@ function checkExpiry({
22244
22459
  }
22245
22460
 
22246
22461
  // scripts/src/lib/blacklist.mjs
22247
- import fs4 from "node:fs";
22248
- import path4 from "node:path";
22249
- import crypto5 from "node:crypto";
22462
+ import fs5 from "node:fs";
22463
+ import path5 from "node:path";
22464
+ import crypto6 from "node:crypto";
22250
22465
  import { fileURLToPath } from "node:url";
22251
22466
  var BlacklistError = class extends Error {
22252
22467
  constructor(code, message) {
@@ -22281,7 +22496,7 @@ function parseBlacklist(doc) {
22281
22496
  }
22282
22497
  addresses.push(e.address);
22283
22498
  }
22284
- const digest = crypto5.createHash("sha256").update(JSON.stringify(addresses), "utf8").digest("hex");
22499
+ const digest = crypto6.createHash("sha256").update(JSON.stringify(addresses), "utf8").digest("hex");
22285
22500
  if (typeof provenance.addressesSha256 !== "string" || !provenance.addressesSha256) {
22286
22501
  throw new BlacklistError("BLACKLIST_UNAVAILABLE", "Blacklist provenance digest is missing.");
22287
22502
  }
@@ -22304,11 +22519,11 @@ function parseBlacklist(doc) {
22304
22519
  return { entries, index: index2, provenance, digest };
22305
22520
  }
22306
22521
  function candidatePaths(moduleUrl) {
22307
- const here = path4.dirname(fileURLToPath(moduleUrl));
22522
+ const here = path5.dirname(fileURLToPath(moduleUrl));
22308
22523
  return [
22309
- path4.join(here, "blacklist.json"),
22310
- path4.join(here, "..", "src", "lib", "blacklist.json"),
22311
- path4.join(here, "..", "..", "src", "lib", "blacklist.json")
22524
+ path5.join(here, "blacklist.json"),
22525
+ path5.join(here, "..", "src", "lib", "blacklist.json"),
22526
+ path5.join(here, "..", "..", "src", "lib", "blacklist.json")
22312
22527
  ];
22313
22528
  }
22314
22529
  var cached = null;
@@ -22319,7 +22534,7 @@ function loadBlacklist(explicitPath) {
22319
22534
  for (const p of paths) {
22320
22535
  let raw;
22321
22536
  try {
22322
- raw = fs4.readFileSync(p, "utf8");
22537
+ raw = fs5.readFileSync(p, "utf8");
22323
22538
  } catch (err) {
22324
22539
  lastErr = err;
22325
22540
  continue;
@@ -22366,199 +22581,6 @@ function assertNotBlacklisted(targets, blacklist) {
22366
22581
  return true;
22367
22582
  }
22368
22583
 
22369
- // scripts/src/lib/state.mjs
22370
- import fs5 from "node:fs";
22371
- import path5 from "node:path";
22372
- import os3 from "node:os";
22373
- import crypto6 from "node:crypto";
22374
- var LOCK_STALE_MS = 6e4;
22375
- var LOCK_WAIT_MS = 1e4;
22376
- var LOCK_POLL_MS = 25;
22377
- function lockPath() {
22378
- return path5.join(stateRoot(), ".send.lock");
22379
- }
22380
- function sleepSync(ms) {
22381
- Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, ms);
22382
- }
22383
- function tryAcquire(file) {
22384
- try {
22385
- const fd = fs5.openSync(file, "wx", 384);
22386
- fs5.writeFileSync(fd, JSON.stringify({ pid: process.pid, at: (/* @__PURE__ */ new Date()).toISOString() }));
22387
- fs5.closeSync(fd);
22388
- return true;
22389
- } catch (err) {
22390
- if (err.code !== "EEXIST") throw err;
22391
- return false;
22392
- }
22393
- }
22394
- function withLock(fn) {
22395
- const file = lockPath();
22396
- fs5.mkdirSync(path5.dirname(file), { recursive: true, mode: 448 });
22397
- const deadline = Date.now() + LOCK_WAIT_MS;
22398
- for (; ; ) {
22399
- if (tryAcquire(file)) break;
22400
- try {
22401
- const age = Date.now() - fs5.statSync(file).mtimeMs;
22402
- if (age > LOCK_STALE_MS) {
22403
- const stolen = `${file}.stale.${crypto6.randomBytes(4).toString("hex")}`;
22404
- try {
22405
- fs5.renameSync(file, stolen);
22406
- fs5.unlinkSync(stolen);
22407
- } catch {
22408
- }
22409
- continue;
22410
- }
22411
- } catch (err) {
22412
- if (err.code !== "ENOENT") throw err;
22413
- continue;
22414
- }
22415
- if (Date.now() > deadline) {
22416
- throw new SkillError(
22417
- "LOCK_TIMEOUT",
22418
- "Another rozo-checkout process is holding the send lock. Refusing to proceed rather than risk a concurrent second send."
22419
- );
22420
- }
22421
- sleepSync(LOCK_POLL_MS);
22422
- }
22423
- try {
22424
- return fn();
22425
- } finally {
22426
- try {
22427
- fs5.unlinkSync(file);
22428
- } catch {
22429
- }
22430
- }
22431
- }
22432
- function stateRoot() {
22433
- return process.env.ROZO_CHECKOUT_STATE_DIR || path5.join(os3.homedir(), ".rozo-checkout", "state");
22434
- }
22435
- function statePath(rozoPaymentId) {
22436
- if (!/^[A-Za-z0-9-]{8,64}$/.test(String(rozoPaymentId || ""))) {
22437
- throw new SkillError("BAD_ROZO_PAYMENT_ID", "Refusing to build a state path from that id.");
22438
- }
22439
- return path5.join(stateRoot(), `${rozoPaymentId}.json`);
22440
- }
22441
- function writeAtomic(file, data) {
22442
- const dir = path5.dirname(file);
22443
- fs5.mkdirSync(dir, { recursive: true, mode: 448 });
22444
- const tmp = path5.join(dir, `.${path5.basename(file)}.${crypto6.randomBytes(6).toString("hex")}.tmp`);
22445
- const fd = fs5.openSync(tmp, "wx", 384);
22446
- try {
22447
- fs5.writeFileSync(fd, JSON.stringify(data, null, 2) + "\n", "utf8");
22448
- fs5.fsyncSync(fd);
22449
- } finally {
22450
- fs5.closeSync(fd);
22451
- }
22452
- fs5.renameSync(tmp, file);
22453
- }
22454
- function readState(rozoPaymentId) {
22455
- const file = statePath(rozoPaymentId);
22456
- let raw;
22457
- try {
22458
- raw = fs5.readFileSync(file, "utf8");
22459
- } catch (err) {
22460
- if (err.code === "ENOENT") return null;
22461
- throw new SkillError("STATE_UNREADABLE", `Cannot read local state: ${err.code}`);
22462
- }
22463
- try {
22464
- return JSON.parse(raw);
22465
- } catch {
22466
- throw new SkillError(
22467
- "STATE_CORRUPT",
22468
- "The local state file for this order is corrupt. Refusing to act; inspect it manually."
22469
- );
22470
- }
22471
- }
22472
- function depositDigest(source) {
22473
- const canonical = JSON.stringify({
22474
- chainId: String(source?.chainId ?? ""),
22475
- tokenSymbol: String(source?.tokenSymbol ?? "").toUpperCase(),
22476
- tokenAddress: String(source?.tokenAddress ?? ""),
22477
- receiverAddress: String(source?.receiverAddress ?? ""),
22478
- receiverMemo: source?.receiverMemo ?? null,
22479
- amount: String(source?.amount ?? ""),
22480
- amountUnit: source?.amountUnit ?? null,
22481
- lnInvoice: source?.lnInvoice ?? null
22482
- });
22483
- return crypto6.createHash("sha256").update(canonical, "utf8").digest("hex");
22484
- }
22485
- function claimSend(rozoPaymentId, intent, { skipCaps = false } = {}) {
22486
- return withLock(() => {
22487
- const state = readState(rozoPaymentId);
22488
- if (!state) {
22489
- throw new SkillError(
22490
- "NO_ORDER_STATE",
22491
- "No local record for this order. Run create-order.js in this same session first."
22492
- );
22493
- }
22494
- if (state.send) {
22495
- throw new SkillError(
22496
- "ALREADY_SENT",
22497
- `A send was already recorded for this order at ${state.send.claimedAt} (status: ${state.send.status}). Refusing to send twice. Check the backend for the pay-in before doing anything else.`,
22498
- { send: state.send }
22499
- );
22500
- }
22501
- const caps = skipCaps ? null : assertPaymentLimit(state.invoiceAmount);
22502
- const next = {
22503
- ...state,
22504
- updatedAt: (/* @__PURE__ */ new Date()).toISOString(),
22505
- send: {
22506
- status: "claimed",
22507
- claimedAt: (/* @__PURE__ */ new Date()).toISOString(),
22508
- chainId: intent.chainId,
22509
- tokenSymbol: intent.tokenSymbol,
22510
- from: intent.from,
22511
- to: intent.to,
22512
- amountAtomic: intent.amountAtomic,
22513
- memo: intent.memo ?? null,
22514
- nonceBefore: intent.nonceBefore ?? null,
22515
- expectedTxHash: intent.expectedTxHash ?? null,
22516
- txHash: null
22517
- }
22518
- };
22519
- writeAtomic(statePath(rozoPaymentId), next);
22520
- return { state: next, caps };
22521
- });
22522
- }
22523
- function recordSendResult(rozoPaymentId, { status, txHash = null, note = null }) {
22524
- return withLock(() => {
22525
- const state = readState(rozoPaymentId);
22526
- if (!state || !state.send) {
22527
- throw new SkillError("NO_SEND_CLAIM", "No send claim to update for this order.");
22528
- }
22529
- const next = {
22530
- ...state,
22531
- updatedAt: (/* @__PURE__ */ new Date()).toISOString(),
22532
- send: {
22533
- ...state.send,
22534
- status,
22535
- txHash: txHash ?? state.send.txHash,
22536
- note,
22537
- resolvedAt: (/* @__PURE__ */ new Date()).toISOString()
22538
- }
22539
- };
22540
- writeAtomic(statePath(rozoPaymentId), next);
22541
- return next;
22542
- });
22543
- }
22544
- var MAX_PAYMENT_USD = 1100;
22545
- function assertPaymentLimit(invoiceUsd) {
22546
- if (invoiceUsd === null || invoiceUsd === void 0 || String(invoiceUsd).trim() === "") {
22547
- throw new SkillError("BAD_INVOICE_AMOUNT", "Cannot evaluate the payment limit without a USD amount.");
22548
- }
22549
- const usd = Number(invoiceUsd);
22550
- if (!Number.isFinite(usd) || usd < 0) {
22551
- throw new SkillError("BAD_INVOICE_AMOUNT", "Cannot evaluate the payment limit without a USD amount.");
22552
- }
22553
- if (usd > MAX_PAYMENT_USD) {
22554
- throw new SkillError(
22555
- "CAP_PER_TX",
22556
- `$${usd} is above the $${MAX_PAYMENT_USD} per-payment limit for automated sending. This limit has no override. Pay this invoice from a wallet you control instead \u2014 that path needs no key and has no limit.`
22557
- );
22558
- }
22559
- return { usd, limitUsd: MAX_PAYMENT_USD };
22560
- }
22561
-
22562
22584
  // scripts/src/lib/bitrefill.mjs
22563
22585
  var BITREFILL_DESTINATION = Object.freeze({ chainId: "8453", tokenSymbol: "USDC" });
22564
22586
  var BITREFILL_MIN_EXPIRY_MS = 5 * 60 * 1e3;
@@ -22802,6 +22824,256 @@ function broadcastOutcome({ receiptStatus, executionError = null, receiptSeen =
22802
22824
  };
22803
22825
  }
22804
22826
 
22827
+ // scripts/src/lib/gas.mjs
22828
+ var EVM_FEE_COIN = Object.freeze({
22829
+ 1: { symbol: "ETH", network: "Ethereum" },
22830
+ 56: { symbol: "BNB", network: "BNB Chain" },
22831
+ 137: { symbol: "POL", network: "Polygon" },
22832
+ 8453: { symbol: "ETH", network: "Base" }
22833
+ });
22834
+ var DEFAULT_GAS_BUFFER_PCT = 20;
22835
+ var MAX_GAS_BUFFER_PCT = 200;
22836
+ function gasBufferPct(env = process.env) {
22837
+ const raw = env?.ROZO_CHECKOUT_GAS_BUFFER_PCT;
22838
+ if (raw === void 0 || raw === "") return DEFAULT_GAS_BUFFER_PCT;
22839
+ const n = Number(raw);
22840
+ if (!Number.isInteger(n) || n < 0 || n > MAX_GAS_BUFFER_PCT) return DEFAULT_GAS_BUFFER_PCT;
22841
+ return n;
22842
+ }
22843
+ function formatAtomic(atomic, decimals) {
22844
+ const neg = atomic < 0n;
22845
+ const v = neg ? -atomic : atomic;
22846
+ const base = 10n ** BigInt(decimals);
22847
+ const whole = v / base;
22848
+ const frac = (v % base).toString().padStart(decimals, "0").replace(/0+$/, "");
22849
+ return `${neg ? "-" : ""}${whole}${frac ? `.${frac}` : ""}`;
22850
+ }
22851
+ function evmFeeRequirement({ gasEstimate, maxFeePerGas, gasPrice, bufferPct = DEFAULT_GAS_BUFFER_PCT }) {
22852
+ const perGas = typeof maxFeePerGas === "bigint" ? maxFeePerGas : typeof gasPrice === "bigint" ? gasPrice : null;
22853
+ if (typeof gasEstimate !== "bigint" || perGas === null) return null;
22854
+ const gasLimit = (gasEstimate * BigInt(100 + bufferPct) + 99n) / 100n;
22855
+ return { gasLimit, perGas, pricing: typeof maxFeePerGas === "bigint" ? "eip1559" : "legacy", fee: gasLimit * perGas };
22856
+ }
22857
+ function assessFee({ balance, required, decimals, symbol, network }) {
22858
+ const ok = required === null ? balance > 0n : balance >= required;
22859
+ const details = {
22860
+ network,
22861
+ feeCoin: symbol,
22862
+ balance: formatAtomic(balance, decimals),
22863
+ required: required === null ? null : formatAtomic(required, decimals),
22864
+ shortfall: required === null || ok ? null : formatAtomic(required - balance, decimals)
22865
+ };
22866
+ return { ok, details };
22867
+ }
22868
+ function insufficient(details, extra) {
22869
+ const need = details.required ? `about ${details.required} ${details.feeCoin}` : `some ${details.feeCoin}`;
22870
+ return new SkillError(
22871
+ "INSUFFICIENT_GAS",
22872
+ `This wallet holds ${details.balance} ${details.feeCoin} on ${details.network} but needs ${need} for the network fee${details.shortfall ? ` (short by ${details.shortfall} ${details.feeCoin})` : ""}. Nothing was signed. Add ${details.feeCoin} on ${details.network} to this wallet and re-run, or pay with a different coin.` + (extra ? ` ${extra}` : ""),
22873
+ details
22874
+ );
22875
+ }
22876
+ function unavailable(network, err) {
22877
+ return new SkillError(
22878
+ "GAS_CHECK_UNAVAILABLE",
22879
+ `Could not check the network fee on ${network}: ${redact(err?.shortMessage || err?.message || "unknown error")}. This is not evidence of a shortfall. Nothing was signed. Retry, pass --rpc <url>, or pay by hand from your wallet.`,
22880
+ { network }
22881
+ );
22882
+ }
22883
+ async function estimateFees(client) {
22884
+ try {
22885
+ return await client.estimateFeesPerGas();
22886
+ } catch (err) {
22887
+ if (err?.name !== "Eip1559FeesNotSupportedError") throw err;
22888
+ return client.estimateFeesPerGas({ type: "legacy" });
22889
+ }
22890
+ }
22891
+ var OP_STACK_GAS_PRICE_ORACLE = "0x420000000000000000000000000000000000000F";
22892
+ var OP_STACK_CHAIN_IDS = /* @__PURE__ */ new Set([8453]);
22893
+ async function evmGasPrecheck({
22894
+ client,
22895
+ chainId,
22896
+ from: from15,
22897
+ to,
22898
+ data,
22899
+ value = 0n,
22900
+ bufferPct = gasBufferPct(),
22901
+ estimateL1Fee: estimateL1Fee2 = null
22902
+ }) {
22903
+ const coin = EVM_FEE_COIN[Number(chainId)] ?? { symbol: "native coin", network: `chain ${chainId}` };
22904
+ let balance;
22905
+ try {
22906
+ balance = await client.getBalance({ address: from15 });
22907
+ } catch (err) {
22908
+ throw unavailable(coin.network, err);
22909
+ }
22910
+ let gasEstimate;
22911
+ let fees;
22912
+ try {
22913
+ gasEstimate = await client.estimateGas({ account: from15, to, data, value });
22914
+ fees = await estimateFees(client);
22915
+ } catch (err) {
22916
+ if (balance === 0n) {
22917
+ throw insufficient(
22918
+ assessFee({ balance, required: null, decimals: 18, symbol: coin.symbol, network: coin.network }).details
22919
+ );
22920
+ }
22921
+ throw unavailable(coin.network, err);
22922
+ }
22923
+ const req = evmFeeRequirement({
22924
+ gasEstimate,
22925
+ maxFeePerGas: fees?.maxFeePerGas,
22926
+ gasPrice: fees?.gasPrice,
22927
+ bufferPct
22928
+ });
22929
+ if (!req) throw unavailable(coin.network, new Error("the node returned no fee price"));
22930
+ let l1Fee = 0n;
22931
+ if (OP_STACK_CHAIN_IDS.has(Number(chainId))) {
22932
+ if (typeof estimateL1Fee2 !== "function") {
22933
+ throw unavailable(coin.network, new Error("no L1 data fee estimator for this OP-stack chain"));
22934
+ }
22935
+ try {
22936
+ l1Fee = (await estimateL1Fee2() * BigInt(100 + bufferPct) + 99n) / 100n;
22937
+ } catch (err) {
22938
+ throw unavailable(coin.network, err);
22939
+ }
22940
+ }
22941
+ const required = req.fee + l1Fee + value;
22942
+ const { ok, details } = assessFee({ balance, required, decimals: 18, symbol: coin.symbol, network: coin.network });
22943
+ const full = {
22944
+ ...details,
22945
+ gasLimit: req.gasLimit.toString(),
22946
+ pricing: req.pricing,
22947
+ bufferPct,
22948
+ l1DataFee: OP_STACK_CHAIN_IDS.has(Number(chainId)) ? formatAtomic(l1Fee, 18) : null
22949
+ };
22950
+ if (!ok) throw insufficient(full);
22951
+ return full;
22952
+ }
22953
+
22954
+ // node_modules/viem/_esm/op-stack/contracts.js
22955
+ var contracts = {
22956
+ gasPriceOracle: { address: "0x420000000000000000000000000000000000000F" },
22957
+ l1Block: { address: "0x4200000000000000000000000000000000000015" },
22958
+ l2CrossDomainMessenger: {
22959
+ address: "0x4200000000000000000000000000000000000007"
22960
+ },
22961
+ l2Erc721Bridge: { address: "0x4200000000000000000000000000000000000014" },
22962
+ l2StandardBridge: { address: "0x4200000000000000000000000000000000000010" },
22963
+ l2ToL1MessagePasser: {
22964
+ address: "0x4200000000000000000000000000000000000016"
22965
+ }
22966
+ };
22967
+
22968
+ // node_modules/viem/_esm/op-stack/abis.js
22969
+ var gasPriceOracleAbi = [
22970
+ { inputs: [], stateMutability: "nonpayable", type: "constructor" },
22971
+ {
22972
+ inputs: [],
22973
+ name: "DECIMALS",
22974
+ outputs: [{ internalType: "uint256", name: "", type: "uint256" }],
22975
+ stateMutability: "view",
22976
+ type: "function"
22977
+ },
22978
+ {
22979
+ inputs: [],
22980
+ name: "baseFee",
22981
+ outputs: [{ internalType: "uint256", name: "", type: "uint256" }],
22982
+ stateMutability: "view",
22983
+ type: "function"
22984
+ },
22985
+ {
22986
+ inputs: [],
22987
+ name: "decimals",
22988
+ outputs: [{ internalType: "uint256", name: "", type: "uint256" }],
22989
+ stateMutability: "pure",
22990
+ type: "function"
22991
+ },
22992
+ {
22993
+ inputs: [],
22994
+ name: "gasPrice",
22995
+ outputs: [{ internalType: "uint256", name: "", type: "uint256" }],
22996
+ stateMutability: "view",
22997
+ type: "function"
22998
+ },
22999
+ {
23000
+ inputs: [{ internalType: "bytes", name: "_data", type: "bytes" }],
23001
+ name: "getL1Fee",
23002
+ outputs: [{ internalType: "uint256", name: "", type: "uint256" }],
23003
+ stateMutability: "view",
23004
+ type: "function"
23005
+ },
23006
+ {
23007
+ inputs: [{ internalType: "bytes", name: "_data", type: "bytes" }],
23008
+ name: "getL1GasUsed",
23009
+ outputs: [{ internalType: "uint256", name: "", type: "uint256" }],
23010
+ stateMutability: "view",
23011
+ type: "function"
23012
+ },
23013
+ {
23014
+ inputs: [],
23015
+ name: "l1BaseFee",
23016
+ outputs: [{ internalType: "uint256", name: "", type: "uint256" }],
23017
+ stateMutability: "view",
23018
+ type: "function"
23019
+ },
23020
+ {
23021
+ inputs: [],
23022
+ name: "overhead",
23023
+ outputs: [{ internalType: "uint256", name: "", type: "uint256" }],
23024
+ stateMutability: "view",
23025
+ type: "function"
23026
+ },
23027
+ {
23028
+ inputs: [],
23029
+ name: "scalar",
23030
+ outputs: [{ internalType: "uint256", name: "", type: "uint256" }],
23031
+ stateMutability: "view",
23032
+ type: "function"
23033
+ },
23034
+ {
23035
+ inputs: [],
23036
+ name: "version",
23037
+ outputs: [{ internalType: "string", name: "", type: "string" }],
23038
+ stateMutability: "view",
23039
+ type: "function"
23040
+ }
23041
+ ];
23042
+
23043
+ // node_modules/viem/_esm/op-stack/actions/estimateL1Fee.js
23044
+ init_getChainContractAddress();
23045
+ async function estimateL1Fee(client, args) {
23046
+ const { chain = client.chain, gasPriceOracleAddress: gasPriceOracleAddress_ } = args;
23047
+ const gasPriceOracleAddress = (() => {
23048
+ if (gasPriceOracleAddress_)
23049
+ return gasPriceOracleAddress_;
23050
+ if (chain)
23051
+ return getChainContractAddress({
23052
+ chain,
23053
+ contract: "gasPriceOracle"
23054
+ });
23055
+ return contracts.gasPriceOracle.address;
23056
+ })();
23057
+ const transaction = serializeTransaction({
23058
+ ...args,
23059
+ chainId: chain?.id ?? 1,
23060
+ type: "eip1559",
23061
+ // Set upper-limit-ish stub values. Shouldn't affect the estimate too much as we are
23062
+ // tweaking dust bytes here (as opposed to long `data` bytes).
23063
+ // See: https://github.com/ethereum-optimism/optimism/blob/54d02df55523c9e1b4b38ed082c12a42087323a0/packages/contracts-bedrock/src/L2/GasPriceOracle.sol#L242-L248.
23064
+ gas: args.data ? 300000n : 21000n,
23065
+ maxFeePerGas: parseGwei("5"),
23066
+ maxPriorityFeePerGas: parseGwei("1"),
23067
+ nonce: 1
23068
+ });
23069
+ return readContract(client, {
23070
+ abi: gasPriceOracleAbi,
23071
+ address: gasPriceOracleAddress,
23072
+ functionName: "getL1Fee",
23073
+ args: [transaction]
23074
+ });
23075
+ }
23076
+
22805
23077
  // node_modules/viem/_esm/accounts/privateKeyToAccount.js
22806
23078
  init_secp256k1();
22807
23079
  init_toHex();
@@ -23073,6 +23345,25 @@ async function main(argv) {
23073
23345
  `Sender holds ${balance} atomic units of ${source.tokenSymbol} on ${chainName(chainId)}; ${amountAtomic} are required.`
23074
23346
  );
23075
23347
  }
23348
+ const data = encodeFunctionData({
23349
+ abi: ERC20_TRANSFER_ABI,
23350
+ functionName: "transfer",
23351
+ args: [to, amountAtomic]
23352
+ });
23353
+ const networkFee = await evmGasPrecheck({
23354
+ client: pub,
23355
+ chainId,
23356
+ from: sender,
23357
+ to: tokenAddress,
23358
+ data,
23359
+ estimateL1Fee: OP_STACK_CHAIN_IDS.has(chainId) ? () => estimateL1Fee(pub, {
23360
+ account: sender,
23361
+ to: tokenAddress,
23362
+ data,
23363
+ chain,
23364
+ gasPriceOracleAddress: OP_STACK_GAS_PRICE_ORACLE
23365
+ }) : null
23366
+ });
23076
23367
  if (dryRun) {
23077
23368
  emit({
23078
23369
  success: true,
@@ -23088,6 +23379,7 @@ async function main(argv) {
23088
23379
  toMasked: maskAddress(to),
23089
23380
  fromMasked: maskAddress(sender)
23090
23381
  },
23382
+ networkFee,
23091
23383
  confirmedAt: state.confirmation?.confirmedAt ?? null,
23092
23384
  keySource,
23093
23385
  // Key NAMES only — a value from a .env is never echoed.
@@ -23097,11 +23389,6 @@ async function main(argv) {
23097
23389
  });
23098
23390
  }
23099
23391
  const wallet = createWalletClient({ account, chain, transport: http(rpcUrl) });
23100
- const data = encodeFunctionData({
23101
- abi: ERC20_TRANSFER_ABI,
23102
- functionName: "transfer",
23103
- args: [to, amountAtomic]
23104
- });
23105
23392
  const request2 = await wallet.prepareTransactionRequest({
23106
23393
  to: tokenAddress,
23107
23394
  data,
@@ -23160,7 +23447,7 @@ async function main(argv) {
23160
23447
  code: landed === false ? "BROADCAST_FAILED" : "BROADCAST_AMBIGUOUS",
23161
23448
  message: redact(err?.shortMessage || err?.message || "broadcast failed")
23162
23449
  },
23163
- signedTxHash: expectedTxHash,
23450
+ signedTxHash: publicHash(expectedTxHash),
23164
23451
  nonceBefore,
23165
23452
  foundOnChain: landed,
23166
23453
  guidance: landed === false ? "Nothing appears to have been broadcast, but this order is now locked against a second automated send. Verify the signed hash on a block explorer first." : "The signed transaction may already be in flight. Do NOT resend. Look up the signed hash above on a block explorer and poll status.js."
@@ -23184,7 +23471,7 @@ async function main(argv) {
23184
23471
  confirmed: false,
23185
23472
  rozoPaymentId,
23186
23473
  linkId: state.linkId,
23187
- txHash,
23474
+ txHash: publicHash(txHash),
23188
23475
  guidance: "Broadcast but not confirmed within the wait window. Do NOT resend. Poll status.js; receipt truth is the backend confirmedAt/amountReceived, not this response."
23189
23476
  },
23190
23477
  EXIT_UNCONFIRMED
@@ -23205,7 +23492,7 @@ async function main(argv) {
23205
23492
  confirmed: succeeded,
23206
23493
  rozoPaymentId,
23207
23494
  linkId: state.linkId,
23208
- txHash,
23495
+ txHash: publicHash(txHash),
23209
23496
  blockNumber: receipt.blockNumber?.toString?.() ?? null,
23210
23497
  ...succeeded ? {} : { error: { code: outcome.code, message: "The transfer reverted on chain." } },
23211
23498
  sent: {