@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.
@@ -32177,7 +32177,20 @@ function redact(text) {
32177
32177
  );
32178
32178
  return s;
32179
32179
  }
32180
+ var PublicHash = class {
32181
+ constructor(value) {
32182
+ this.value = value;
32183
+ }
32184
+ toJSON() {
32185
+ return this.value;
32186
+ }
32187
+ };
32188
+ var TX_HASH_VALUE = /^(0x[0-9a-fA-F]{64}|[1-9A-HJ-NP-Za-km-z]{86,88})$/;
32189
+ function publicHash(value) {
32190
+ return typeof value === "string" && TX_HASH_VALUE.test(value) ? new PublicHash(value) : value;
32191
+ }
32180
32192
  function redactDeep(value) {
32193
+ if (value instanceof PublicHash) return value.value;
32181
32194
  if (typeof value === "string") return redact(value);
32182
32195
  if (Array.isArray(value)) return value.map(redactDeep);
32183
32196
  if (value && typeof value === "object") {
@@ -33081,6 +33094,199 @@ function getJson(url, opts) {
33081
33094
  return request("GET", url, opts);
33082
33095
  }
33083
33096
 
33097
+ // scripts/src/lib/state.mjs
33098
+ import fs4 from "node:fs";
33099
+ import path4 from "node:path";
33100
+ import os3 from "node:os";
33101
+ import crypto4 from "node:crypto";
33102
+ var LOCK_STALE_MS = 6e4;
33103
+ var LOCK_WAIT_MS = 1e4;
33104
+ var LOCK_POLL_MS = 25;
33105
+ function lockPath() {
33106
+ return path4.join(stateRoot(), ".send.lock");
33107
+ }
33108
+ function sleepSync(ms) {
33109
+ Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, ms);
33110
+ }
33111
+ function tryAcquire(file) {
33112
+ try {
33113
+ const fd = fs4.openSync(file, "wx", 384);
33114
+ fs4.writeFileSync(fd, JSON.stringify({ pid: process.pid, at: (/* @__PURE__ */ new Date()).toISOString() }));
33115
+ fs4.closeSync(fd);
33116
+ return true;
33117
+ } catch (err) {
33118
+ if (err.code !== "EEXIST") throw err;
33119
+ return false;
33120
+ }
33121
+ }
33122
+ function withLock(fn) {
33123
+ const file = lockPath();
33124
+ fs4.mkdirSync(path4.dirname(file), { recursive: true, mode: 448 });
33125
+ const deadline = Date.now() + LOCK_WAIT_MS;
33126
+ for (; ; ) {
33127
+ if (tryAcquire(file)) break;
33128
+ try {
33129
+ const age = Date.now() - fs4.statSync(file).mtimeMs;
33130
+ if (age > LOCK_STALE_MS) {
33131
+ const stolen = `${file}.stale.${crypto4.randomBytes(4).toString("hex")}`;
33132
+ try {
33133
+ fs4.renameSync(file, stolen);
33134
+ fs4.unlinkSync(stolen);
33135
+ } catch {
33136
+ }
33137
+ continue;
33138
+ }
33139
+ } catch (err) {
33140
+ if (err.code !== "ENOENT") throw err;
33141
+ continue;
33142
+ }
33143
+ if (Date.now() > deadline) {
33144
+ throw new SkillError(
33145
+ "LOCK_TIMEOUT",
33146
+ "Another rozo-checkout process is holding the send lock. Refusing to proceed rather than risk a concurrent second send."
33147
+ );
33148
+ }
33149
+ sleepSync(LOCK_POLL_MS);
33150
+ }
33151
+ try {
33152
+ return fn();
33153
+ } finally {
33154
+ try {
33155
+ fs4.unlinkSync(file);
33156
+ } catch {
33157
+ }
33158
+ }
33159
+ }
33160
+ function stateRoot() {
33161
+ return process.env.ROZO_CHECKOUT_STATE_DIR || path4.join(os3.homedir(), ".rozo-checkout", "state");
33162
+ }
33163
+ function statePath(rozoPaymentId) {
33164
+ if (!/^[A-Za-z0-9-]{8,64}$/.test(String(rozoPaymentId || ""))) {
33165
+ throw new SkillError("BAD_ROZO_PAYMENT_ID", "Refusing to build a state path from that id.");
33166
+ }
33167
+ return path4.join(stateRoot(), `${rozoPaymentId}.json`);
33168
+ }
33169
+ function writeAtomic(file, data) {
33170
+ const dir = path4.dirname(file);
33171
+ fs4.mkdirSync(dir, { recursive: true, mode: 448 });
33172
+ const tmp = path4.join(dir, `.${path4.basename(file)}.${crypto4.randomBytes(6).toString("hex")}.tmp`);
33173
+ const fd = fs4.openSync(tmp, "wx", 384);
33174
+ try {
33175
+ fs4.writeFileSync(fd, JSON.stringify(data, null, 2) + "\n", "utf8");
33176
+ fs4.fsyncSync(fd);
33177
+ } finally {
33178
+ fs4.closeSync(fd);
33179
+ }
33180
+ fs4.renameSync(tmp, file);
33181
+ }
33182
+ function readState(rozoPaymentId) {
33183
+ const file = statePath(rozoPaymentId);
33184
+ let raw;
33185
+ try {
33186
+ raw = fs4.readFileSync(file, "utf8");
33187
+ } catch (err) {
33188
+ if (err.code === "ENOENT") return null;
33189
+ throw new SkillError("STATE_UNREADABLE", `Cannot read local state: ${err.code}`);
33190
+ }
33191
+ try {
33192
+ return JSON.parse(raw);
33193
+ } catch {
33194
+ throw new SkillError(
33195
+ "STATE_CORRUPT",
33196
+ "The local state file for this order is corrupt. Refusing to act; inspect it manually."
33197
+ );
33198
+ }
33199
+ }
33200
+ function depositDigest(source) {
33201
+ const canonical = JSON.stringify({
33202
+ chainId: String(source?.chainId ?? ""),
33203
+ tokenSymbol: String(source?.tokenSymbol ?? "").toUpperCase(),
33204
+ tokenAddress: String(source?.tokenAddress ?? ""),
33205
+ receiverAddress: String(source?.receiverAddress ?? ""),
33206
+ receiverMemo: source?.receiverMemo ?? null,
33207
+ amount: String(source?.amount ?? ""),
33208
+ amountUnit: source?.amountUnit ?? null,
33209
+ lnInvoice: source?.lnInvoice ?? null
33210
+ });
33211
+ return crypto4.createHash("sha256").update(canonical, "utf8").digest("hex");
33212
+ }
33213
+ function claimSend(rozoPaymentId, intent, { skipCaps = false } = {}) {
33214
+ return withLock(() => {
33215
+ const state = readState(rozoPaymentId);
33216
+ if (!state) {
33217
+ throw new SkillError(
33218
+ "NO_ORDER_STATE",
33219
+ "No local record for this order. Run create-order.js in this same session first."
33220
+ );
33221
+ }
33222
+ if (state.send) {
33223
+ throw new SkillError(
33224
+ "ALREADY_SENT",
33225
+ `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.`,
33226
+ { send: state.send }
33227
+ );
33228
+ }
33229
+ const caps = skipCaps ? null : assertPaymentLimit(state.invoiceAmount);
33230
+ const next = {
33231
+ ...state,
33232
+ updatedAt: (/* @__PURE__ */ new Date()).toISOString(),
33233
+ send: {
33234
+ status: "claimed",
33235
+ claimedAt: (/* @__PURE__ */ new Date()).toISOString(),
33236
+ chainId: intent.chainId,
33237
+ tokenSymbol: intent.tokenSymbol,
33238
+ from: intent.from,
33239
+ to: intent.to,
33240
+ amountAtomic: intent.amountAtomic,
33241
+ memo: intent.memo ?? null,
33242
+ nonceBefore: intent.nonceBefore ?? null,
33243
+ expectedTxHash: intent.expectedTxHash ?? null,
33244
+ txHash: null
33245
+ }
33246
+ };
33247
+ writeAtomic(statePath(rozoPaymentId), next);
33248
+ return { state: next, caps };
33249
+ });
33250
+ }
33251
+ function recordSendResult(rozoPaymentId, { status, txHash = null, note = null }) {
33252
+ return withLock(() => {
33253
+ const state = readState(rozoPaymentId);
33254
+ if (!state || !state.send) {
33255
+ throw new SkillError("NO_SEND_CLAIM", "No send claim to update for this order.");
33256
+ }
33257
+ const next = {
33258
+ ...state,
33259
+ updatedAt: (/* @__PURE__ */ new Date()).toISOString(),
33260
+ send: {
33261
+ ...state.send,
33262
+ status,
33263
+ txHash: txHash ?? state.send.txHash,
33264
+ note,
33265
+ resolvedAt: (/* @__PURE__ */ new Date()).toISOString()
33266
+ }
33267
+ };
33268
+ writeAtomic(statePath(rozoPaymentId), next);
33269
+ return next;
33270
+ });
33271
+ }
33272
+ var MAX_PAYMENT_USD = 1100;
33273
+ function assertPaymentLimit(invoiceUsd) {
33274
+ if (invoiceUsd === null || invoiceUsd === void 0 || String(invoiceUsd).trim() === "") {
33275
+ throw new SkillError("BAD_INVOICE_AMOUNT", "Cannot evaluate the payment limit without a USD amount.");
33276
+ }
33277
+ const usd = Number(invoiceUsd);
33278
+ if (!Number.isFinite(usd) || usd < 0) {
33279
+ throw new SkillError("BAD_INVOICE_AMOUNT", "Cannot evaluate the payment limit without a USD amount.");
33280
+ }
33281
+ if (usd > MAX_PAYMENT_USD) {
33282
+ throw new SkillError(
33283
+ "CAP_PER_TX",
33284
+ `$${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.`
33285
+ );
33286
+ }
33287
+ return { usd, limitUsd: MAX_PAYMENT_USD };
33288
+ }
33289
+
33084
33290
  // scripts/src/lib/api.mjs
33085
33291
  var PKG_VERSION = (() => {
33086
33292
  const requireFrom = createRequire(import.meta.url);
@@ -33430,9 +33636,9 @@ function checkExpiry({
33430
33636
  }
33431
33637
 
33432
33638
  // scripts/src/lib/blacklist.mjs
33433
- import fs4 from "node:fs";
33434
- import path4 from "node:path";
33435
- import crypto4 from "node:crypto";
33639
+ import fs5 from "node:fs";
33640
+ import path5 from "node:path";
33641
+ import crypto5 from "node:crypto";
33436
33642
  import { fileURLToPath } from "node:url";
33437
33643
  var BlacklistError = class extends Error {
33438
33644
  constructor(code, message) {
@@ -33467,7 +33673,7 @@ function parseBlacklist(doc) {
33467
33673
  }
33468
33674
  addresses.push(e.address);
33469
33675
  }
33470
- const digest = crypto4.createHash("sha256").update(JSON.stringify(addresses), "utf8").digest("hex");
33676
+ const digest = crypto5.createHash("sha256").update(JSON.stringify(addresses), "utf8").digest("hex");
33471
33677
  if (typeof provenance.addressesSha256 !== "string" || !provenance.addressesSha256) {
33472
33678
  throw new BlacklistError("BLACKLIST_UNAVAILABLE", "Blacklist provenance digest is missing.");
33473
33679
  }
@@ -33490,11 +33696,11 @@ function parseBlacklist(doc) {
33490
33696
  return { entries, index, provenance, digest };
33491
33697
  }
33492
33698
  function candidatePaths(moduleUrl) {
33493
- const here = path4.dirname(fileURLToPath(moduleUrl));
33699
+ const here = path5.dirname(fileURLToPath(moduleUrl));
33494
33700
  return [
33495
- path4.join(here, "blacklist.json"),
33496
- path4.join(here, "..", "src", "lib", "blacklist.json"),
33497
- path4.join(here, "..", "..", "src", "lib", "blacklist.json")
33701
+ path5.join(here, "blacklist.json"),
33702
+ path5.join(here, "..", "src", "lib", "blacklist.json"),
33703
+ path5.join(here, "..", "..", "src", "lib", "blacklist.json")
33498
33704
  ];
33499
33705
  }
33500
33706
  var cached = null;
@@ -33505,7 +33711,7 @@ function loadBlacklist(explicitPath) {
33505
33711
  for (const p of paths) {
33506
33712
  let raw;
33507
33713
  try {
33508
- raw = fs4.readFileSync(p, "utf8");
33714
+ raw = fs5.readFileSync(p, "utf8");
33509
33715
  } catch (err) {
33510
33716
  lastErr = err;
33511
33717
  continue;
@@ -33552,199 +33758,6 @@ function assertNotBlacklisted(targets, blacklist) {
33552
33758
  return true;
33553
33759
  }
33554
33760
 
33555
- // scripts/src/lib/state.mjs
33556
- import fs5 from "node:fs";
33557
- import path5 from "node:path";
33558
- import os3 from "node:os";
33559
- import crypto5 from "node:crypto";
33560
- var LOCK_STALE_MS = 6e4;
33561
- var LOCK_WAIT_MS = 1e4;
33562
- var LOCK_POLL_MS = 25;
33563
- function lockPath() {
33564
- return path5.join(stateRoot(), ".send.lock");
33565
- }
33566
- function sleepSync(ms) {
33567
- Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, ms);
33568
- }
33569
- function tryAcquire(file) {
33570
- try {
33571
- const fd = fs5.openSync(file, "wx", 384);
33572
- fs5.writeFileSync(fd, JSON.stringify({ pid: process.pid, at: (/* @__PURE__ */ new Date()).toISOString() }));
33573
- fs5.closeSync(fd);
33574
- return true;
33575
- } catch (err) {
33576
- if (err.code !== "EEXIST") throw err;
33577
- return false;
33578
- }
33579
- }
33580
- function withLock(fn) {
33581
- const file = lockPath();
33582
- fs5.mkdirSync(path5.dirname(file), { recursive: true, mode: 448 });
33583
- const deadline = Date.now() + LOCK_WAIT_MS;
33584
- for (; ; ) {
33585
- if (tryAcquire(file)) break;
33586
- try {
33587
- const age = Date.now() - fs5.statSync(file).mtimeMs;
33588
- if (age > LOCK_STALE_MS) {
33589
- const stolen = `${file}.stale.${crypto5.randomBytes(4).toString("hex")}`;
33590
- try {
33591
- fs5.renameSync(file, stolen);
33592
- fs5.unlinkSync(stolen);
33593
- } catch {
33594
- }
33595
- continue;
33596
- }
33597
- } catch (err) {
33598
- if (err.code !== "ENOENT") throw err;
33599
- continue;
33600
- }
33601
- if (Date.now() > deadline) {
33602
- throw new SkillError(
33603
- "LOCK_TIMEOUT",
33604
- "Another rozo-checkout process is holding the send lock. Refusing to proceed rather than risk a concurrent second send."
33605
- );
33606
- }
33607
- sleepSync(LOCK_POLL_MS);
33608
- }
33609
- try {
33610
- return fn();
33611
- } finally {
33612
- try {
33613
- fs5.unlinkSync(file);
33614
- } catch {
33615
- }
33616
- }
33617
- }
33618
- function stateRoot() {
33619
- return process.env.ROZO_CHECKOUT_STATE_DIR || path5.join(os3.homedir(), ".rozo-checkout", "state");
33620
- }
33621
- function statePath(rozoPaymentId) {
33622
- if (!/^[A-Za-z0-9-]{8,64}$/.test(String(rozoPaymentId || ""))) {
33623
- throw new SkillError("BAD_ROZO_PAYMENT_ID", "Refusing to build a state path from that id.");
33624
- }
33625
- return path5.join(stateRoot(), `${rozoPaymentId}.json`);
33626
- }
33627
- function writeAtomic(file, data) {
33628
- const dir = path5.dirname(file);
33629
- fs5.mkdirSync(dir, { recursive: true, mode: 448 });
33630
- const tmp = path5.join(dir, `.${path5.basename(file)}.${crypto5.randomBytes(6).toString("hex")}.tmp`);
33631
- const fd = fs5.openSync(tmp, "wx", 384);
33632
- try {
33633
- fs5.writeFileSync(fd, JSON.stringify(data, null, 2) + "\n", "utf8");
33634
- fs5.fsyncSync(fd);
33635
- } finally {
33636
- fs5.closeSync(fd);
33637
- }
33638
- fs5.renameSync(tmp, file);
33639
- }
33640
- function readState(rozoPaymentId) {
33641
- const file = statePath(rozoPaymentId);
33642
- let raw;
33643
- try {
33644
- raw = fs5.readFileSync(file, "utf8");
33645
- } catch (err) {
33646
- if (err.code === "ENOENT") return null;
33647
- throw new SkillError("STATE_UNREADABLE", `Cannot read local state: ${err.code}`);
33648
- }
33649
- try {
33650
- return JSON.parse(raw);
33651
- } catch {
33652
- throw new SkillError(
33653
- "STATE_CORRUPT",
33654
- "The local state file for this order is corrupt. Refusing to act; inspect it manually."
33655
- );
33656
- }
33657
- }
33658
- function depositDigest(source) {
33659
- const canonical = JSON.stringify({
33660
- chainId: String(source?.chainId ?? ""),
33661
- tokenSymbol: String(source?.tokenSymbol ?? "").toUpperCase(),
33662
- tokenAddress: String(source?.tokenAddress ?? ""),
33663
- receiverAddress: String(source?.receiverAddress ?? ""),
33664
- receiverMemo: source?.receiverMemo ?? null,
33665
- amount: String(source?.amount ?? ""),
33666
- amountUnit: source?.amountUnit ?? null,
33667
- lnInvoice: source?.lnInvoice ?? null
33668
- });
33669
- return crypto5.createHash("sha256").update(canonical, "utf8").digest("hex");
33670
- }
33671
- function claimSend(rozoPaymentId, intent, { skipCaps = false } = {}) {
33672
- return withLock(() => {
33673
- const state = readState(rozoPaymentId);
33674
- if (!state) {
33675
- throw new SkillError(
33676
- "NO_ORDER_STATE",
33677
- "No local record for this order. Run create-order.js in this same session first."
33678
- );
33679
- }
33680
- if (state.send) {
33681
- throw new SkillError(
33682
- "ALREADY_SENT",
33683
- `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.`,
33684
- { send: state.send }
33685
- );
33686
- }
33687
- const caps = skipCaps ? null : assertPaymentLimit(state.invoiceAmount);
33688
- const next = {
33689
- ...state,
33690
- updatedAt: (/* @__PURE__ */ new Date()).toISOString(),
33691
- send: {
33692
- status: "claimed",
33693
- claimedAt: (/* @__PURE__ */ new Date()).toISOString(),
33694
- chainId: intent.chainId,
33695
- tokenSymbol: intent.tokenSymbol,
33696
- from: intent.from,
33697
- to: intent.to,
33698
- amountAtomic: intent.amountAtomic,
33699
- memo: intent.memo ?? null,
33700
- nonceBefore: intent.nonceBefore ?? null,
33701
- expectedTxHash: intent.expectedTxHash ?? null,
33702
- txHash: null
33703
- }
33704
- };
33705
- writeAtomic(statePath(rozoPaymentId), next);
33706
- return { state: next, caps };
33707
- });
33708
- }
33709
- function recordSendResult(rozoPaymentId, { status, txHash = null, note = null }) {
33710
- return withLock(() => {
33711
- const state = readState(rozoPaymentId);
33712
- if (!state || !state.send) {
33713
- throw new SkillError("NO_SEND_CLAIM", "No send claim to update for this order.");
33714
- }
33715
- const next = {
33716
- ...state,
33717
- updatedAt: (/* @__PURE__ */ new Date()).toISOString(),
33718
- send: {
33719
- ...state.send,
33720
- status,
33721
- txHash: txHash ?? state.send.txHash,
33722
- note,
33723
- resolvedAt: (/* @__PURE__ */ new Date()).toISOString()
33724
- }
33725
- };
33726
- writeAtomic(statePath(rozoPaymentId), next);
33727
- return next;
33728
- });
33729
- }
33730
- var MAX_PAYMENT_USD = 1100;
33731
- function assertPaymentLimit(invoiceUsd) {
33732
- if (invoiceUsd === null || invoiceUsd === void 0 || String(invoiceUsd).trim() === "") {
33733
- throw new SkillError("BAD_INVOICE_AMOUNT", "Cannot evaluate the payment limit without a USD amount.");
33734
- }
33735
- const usd = Number(invoiceUsd);
33736
- if (!Number.isFinite(usd) || usd < 0) {
33737
- throw new SkillError("BAD_INVOICE_AMOUNT", "Cannot evaluate the payment limit without a USD amount.");
33738
- }
33739
- if (usd > MAX_PAYMENT_USD) {
33740
- throw new SkillError(
33741
- "CAP_PER_TX",
33742
- `$${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.`
33743
- );
33744
- }
33745
- return { usd, limitUsd: MAX_PAYMENT_USD };
33746
- }
33747
-
33748
33761
  // scripts/src/lib/bitrefill.mjs
33749
33762
  var BITREFILL_DESTINATION = Object.freeze({ chainId: "8453", tokenSymbol: "USDC" });
33750
33763
  var BITREFILL_MIN_EXPIRY_MS = 5 * 60 * 1e3;
@@ -33988,6 +34001,82 @@ function broadcastOutcome({ receiptStatus, executionError = null, receiptSeen =
33988
34001
  };
33989
34002
  }
33990
34003
 
34004
+ // scripts/src/lib/gas.mjs
34005
+ var EVM_FEE_COIN = Object.freeze({
34006
+ 1: { symbol: "ETH", network: "Ethereum" },
34007
+ 56: { symbol: "BNB", network: "BNB Chain" },
34008
+ 137: { symbol: "POL", network: "Polygon" },
34009
+ 8453: { symbol: "ETH", network: "Base" }
34010
+ });
34011
+ function formatAtomic(atomic, decimals) {
34012
+ const neg = atomic < 0n;
34013
+ const v = neg ? -atomic : atomic;
34014
+ const base = 10n ** BigInt(decimals);
34015
+ const whole = v / base;
34016
+ const frac = (v % base).toString().padStart(decimals, "0").replace(/0+$/, "");
34017
+ return `${neg ? "-" : ""}${whole}${frac ? `.${frac}` : ""}`;
34018
+ }
34019
+ function assessFee({ balance, required, decimals, symbol, network }) {
34020
+ const ok = required === null ? balance > 0n : balance >= required;
34021
+ const details = {
34022
+ network,
34023
+ feeCoin: symbol,
34024
+ balance: formatAtomic(balance, decimals),
34025
+ required: required === null ? null : formatAtomic(required, decimals),
34026
+ shortfall: required === null || ok ? null : formatAtomic(required - balance, decimals)
34027
+ };
34028
+ return { ok, details };
34029
+ }
34030
+ function insufficient(details, extra) {
34031
+ const need = details.required ? `about ${details.required} ${details.feeCoin}` : `some ${details.feeCoin}`;
34032
+ return new SkillError(
34033
+ "INSUFFICIENT_GAS",
34034
+ `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}` : ""),
34035
+ details
34036
+ );
34037
+ }
34038
+ function unavailable(network, err) {
34039
+ return new SkillError(
34040
+ "GAS_CHECK_UNAVAILABLE",
34041
+ `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.`,
34042
+ { network }
34043
+ );
34044
+ }
34045
+ var LAMPORTS_DECIMALS = 9;
34046
+ async function solFeePrecheck({ connection, payer, message }) {
34047
+ const network = "Solana";
34048
+ let balance;
34049
+ let fee;
34050
+ let rentMin;
34051
+ try {
34052
+ balance = BigInt(await connection.getBalance(payer, "confirmed"));
34053
+ const res = await connection.getFeeForMessage(message, "confirmed");
34054
+ if (res?.value === null || res?.value === void 0) {
34055
+ throw new Error("the node returned no fee for this transaction (stale blockhash?)");
34056
+ }
34057
+ fee = BigInt(res.value);
34058
+ rentMin = BigInt(await connection.getMinimumBalanceForRentExemption(0));
34059
+ } catch (err) {
34060
+ if (balance === 0n) {
34061
+ throw insufficient(
34062
+ assessFee({ balance, required: null, decimals: LAMPORTS_DECIMALS, symbol: "SOL", network }).details
34063
+ );
34064
+ }
34065
+ throw unavailable(network, err);
34066
+ }
34067
+ const remaining = balance - fee;
34068
+ const required = remaining === 0n ? fee : fee + rentMin;
34069
+ const { ok, details } = assessFee({ balance, required, decimals: LAMPORTS_DECIMALS, symbol: "SOL", network });
34070
+ const full = { ...details, fee: formatAtomic(fee, LAMPORTS_DECIMALS), rentExemptMinimum: formatAtomic(rentMin, LAMPORTS_DECIMALS) };
34071
+ if (!ok) {
34072
+ throw insufficient(
34073
+ full,
34074
+ balance >= fee ? "Solana also requires a wallet to keep a small minimum balance after paying the fee." : null
34075
+ );
34076
+ }
34077
+ return full;
34078
+ }
34079
+
33991
34080
  // scripts/src/send-sol.mjs
33992
34081
  var import_web32 = __toESM(require_index_cjs(), 1);
33993
34082
 
@@ -34152,6 +34241,34 @@ async function main(argv) {
34152
34241
  );
34153
34242
  }
34154
34243
  }
34244
+ const tx = new import_web32.Transaction();
34245
+ tx.add(
34246
+ createTransferCheckedInstruction(
34247
+ fromAta,
34248
+ mint,
34249
+ toAta,
34250
+ keypair.publicKey,
34251
+ amountAtomic,
34252
+ mintInfo.decimals
34253
+ )
34254
+ );
34255
+ if (source.receiverMemo) {
34256
+ tx.add(
34257
+ new import_web32.TransactionInstruction({
34258
+ keys: [],
34259
+ programId: MEMO_PROGRAM_ID,
34260
+ data: Buffer.from(String(source.receiverMemo), "utf8")
34261
+ })
34262
+ );
34263
+ }
34264
+ const { blockhash, lastValidBlockHeight } = await connection.getLatestBlockhash("confirmed");
34265
+ tx.recentBlockhash = blockhash;
34266
+ tx.feePayer = keypair.publicKey;
34267
+ const networkFee = await solFeePrecheck({
34268
+ connection,
34269
+ payer: keypair.publicKey,
34270
+ message: tx.compileMessage()
34271
+ });
34155
34272
  if (dryRun) {
34156
34273
  emit({
34157
34274
  success: true,
@@ -34168,6 +34285,7 @@ async function main(argv) {
34168
34285
  toIsTokenAccount,
34169
34286
  withMemo: Boolean(source.receiverMemo)
34170
34287
  },
34288
+ networkFee,
34171
34289
  confirmedAt: state.confirmation?.confirmedAt ?? null,
34172
34290
  keySource,
34173
34291
  // Key NAMES only — a value from a .env is never echoed.
@@ -34176,29 +34294,6 @@ async function main(argv) {
34176
34294
  note: "Nothing was signed or broadcast. Add --send (without --dry-run) to execute."
34177
34295
  });
34178
34296
  }
34179
- const tx = new import_web32.Transaction();
34180
- tx.add(
34181
- createTransferCheckedInstruction(
34182
- fromAta,
34183
- mint,
34184
- toAta,
34185
- keypair.publicKey,
34186
- amountAtomic,
34187
- mintInfo.decimals
34188
- )
34189
- );
34190
- if (source.receiverMemo) {
34191
- tx.add(
34192
- new import_web32.TransactionInstruction({
34193
- keys: [],
34194
- programId: MEMO_PROGRAM_ID,
34195
- data: Buffer.from(String(source.receiverMemo), "utf8")
34196
- })
34197
- );
34198
- }
34199
- const { blockhash, lastValidBlockHeight } = await connection.getLatestBlockhash("confirmed");
34200
- tx.recentBlockhash = blockhash;
34201
- tx.feePayer = keypair.publicKey;
34202
34297
  tx.sign(keypair);
34203
34298
  const signature = tx.signatures[0]?.signature ? base58Encode(tx.signatures[0].signature) : null;
34204
34299
  await finalPayabilityCheck({
@@ -34250,7 +34345,7 @@ async function main(argv) {
34250
34345
  code: landed ? "BROADCAST_AMBIGUOUS" : "BROADCAST_FAILED",
34251
34346
  message: redact(err?.message || "broadcast failed")
34252
34347
  },
34253
- signature,
34348
+ signature: publicHash(signature),
34254
34349
  guidance: landed ? "The signed transaction may already be on chain. Do NOT resend. Check the signature on an explorer and poll status.js." : "Nothing appears to have landed, but this order is now locked against a second automated send. Verify on chain before doing anything else."
34255
34350
  },
34256
34351
  EXIT_ERROR
@@ -34287,7 +34382,7 @@ async function main(argv) {
34287
34382
  confirmed: false,
34288
34383
  rozoPaymentId,
34289
34384
  linkId: state.linkId,
34290
- txHash: sent,
34385
+ txHash: publicHash(sent),
34291
34386
  error: {
34292
34387
  code: broadcastOutcome({ executionError }).code,
34293
34388
  message: `The transaction landed but failed on chain: ${redact(JSON.stringify(executionError))}`
@@ -34306,7 +34401,7 @@ async function main(argv) {
34306
34401
  confirmed: false,
34307
34402
  rozoPaymentId,
34308
34403
  linkId: state.linkId,
34309
- txHash: sent,
34404
+ txHash: publicHash(sent),
34310
34405
  guidance: "Broadcast but not confirmed within the wait window. Do NOT resend. Poll status.js; receipt truth is the backend confirmedAt/amountReceived."
34311
34406
  },
34312
34407
  EXIT_UNCONFIRMED
@@ -34320,7 +34415,7 @@ async function main(argv) {
34320
34415
  confirmed: true,
34321
34416
  rozoPaymentId,
34322
34417
  linkId: state.linkId,
34323
- txHash: sent,
34418
+ txHash: publicHash(sent),
34324
34419
  sent: {
34325
34420
  chain: chainName(900),
34326
34421
  tokenSymbol: source.tokenSymbol,