@rozoai/checkout 0.1.15 → 0.1.16

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.
@@ -3475,6 +3475,9 @@ function carry(digits) {
3475
3475
  }
3476
3476
  return `1${out2.join("")}`;
3477
3477
  }
3478
+ function fromGwei(gwei, unit = "wei") {
3479
+ return from(gwei, exponents.gwei - exponents[unit]);
3480
+ }
3478
3481
  var exponents, InvalidDecimalNumberError, InvalidDecimalsError;
3479
3482
  var init_Value = __esm({
3480
3483
  "node_modules/viem/_esm/utils/unit/Value.js"() {
@@ -20541,7 +20544,7 @@ var require_dist = __commonJS({
20541
20544
  }
20542
20545
  }
20543
20546
  }
20544
- function* run7(value, struct2, options = {}) {
20547
+ function* run8(value, struct2, options = {}) {
20545
20548
  const { path: path7 = [], branch = [value], coerce: coerce2 = false, mask: mask2 = false } = options;
20546
20549
  const ctx = { path: path7, branch, mask: mask2 };
20547
20550
  if (coerce2) {
@@ -20554,7 +20557,7 @@ var require_dist = __commonJS({
20554
20557
  yield [failure, void 0];
20555
20558
  }
20556
20559
  for (let [k, v, s] of struct2.entries(value, ctx)) {
20557
- const ts = run7(v, s, {
20560
+ const ts = run8(v, s, {
20558
20561
  path: k === void 0 ? path7 : [...path7, k],
20559
20562
  branch: k === void 0 ? branch : [...branch, v],
20560
20563
  coerce: coerce2,
@@ -20682,7 +20685,7 @@ var require_dist = __commonJS({
20682
20685
  return !result[0];
20683
20686
  }
20684
20687
  function validate7(value, struct2, options = {}) {
20685
- const tuples = run7(value, struct2, options);
20688
+ const tuples = run8(value, struct2, options);
20686
20689
  const tuple2 = shiftIterator(tuples);
20687
20690
  if (tuple2[0]) {
20688
20691
  const error = new StructError(tuple2[0], function* () {
@@ -21095,7 +21098,7 @@ var require_dist = __commonJS({
21095
21098
  validator(value, ctx) {
21096
21099
  const failures = [];
21097
21100
  for (const S of Structs) {
21098
- const [...tuples] = run7(value, S, ctx);
21101
+ const [...tuples] = run8(value, S, ctx);
21099
21102
  const [first] = tuples;
21100
21103
  if (!first[0]) {
21101
21104
  return [];
@@ -41511,7 +41514,20 @@ function redact(text) {
41511
41514
  );
41512
41515
  return s;
41513
41516
  }
41517
+ var PublicHash = class {
41518
+ constructor(value) {
41519
+ this.value = value;
41520
+ }
41521
+ toJSON() {
41522
+ return this.value;
41523
+ }
41524
+ };
41525
+ var TX_HASH_VALUE = /^(0x[0-9a-fA-F]{64}|[1-9A-HJ-NP-Za-km-z]{86,88})$/;
41526
+ function publicHash(value) {
41527
+ return typeof value === "string" && TX_HASH_VALUE.test(value) ? new PublicHash(value) : value;
41528
+ }
41514
41529
  function redactDeep(value) {
41530
+ if (value instanceof PublicHash) return value.value;
41515
41531
  if (typeof value === "string") return redact(value);
41516
41532
  if (Array.isArray(value)) return value.map(redactDeep);
41517
41533
  if (value && typeof value === "object") {
@@ -41754,7 +41770,7 @@ function normalizeUtmSource(value) {
41754
41770
  }
41755
41771
 
41756
41772
  // scripts/src/lib/cli-args.mjs
41757
- var COMMANDS = ["pay", "quote", "status", "help", "version"];
41773
+ var COMMANDS = ["pay", "quote", "status", "receipt", "help", "version"];
41758
41774
  var CHAIN_ALIASES = {
41759
41775
  ethereum: "1",
41760
41776
  eth: "1",
@@ -41970,6 +41986,18 @@ function parseCliArgs(argv) {
41970
41986
  if (!target) throw new CliError("MISSING_TARGET", "Usage: rozo-checkout quote <coinbase-link>");
41971
41987
  return { command, target, json, ...utmSource ? { utmSource } : {} };
41972
41988
  }
41989
+ if (command === "receipt") {
41990
+ if (!target) {
41991
+ throw new CliError(
41992
+ "MISSING_TARGET",
41993
+ "Usage: rozo-checkout receipt <rozoPaymentId | coinbase-link>"
41994
+ );
41995
+ }
41996
+ if (flags.provider !== void 0 && !["coinbase", "bitrefill"].includes(String(flags.provider))) {
41997
+ throw new CliError("BAD_VALUE", "--provider must be coinbase or bitrefill.");
41998
+ }
41999
+ return { command, target, json, provider: flags.provider };
42000
+ }
41973
42001
  if (command === "status") {
41974
42002
  if (!target) {
41975
42003
  throw new CliError(
@@ -42087,6 +42115,7 @@ USAGE
42087
42115
  npx @rozoai/checkout pay --bitrefill-invoice <id> --to <0x\u2026> --amount <USDC> --expires-at <ISO> --with <coin>
42088
42116
  npx @rozoai/checkout quote <coinbase-link>
42089
42117
  npx @rozoai/checkout status <rozoPaymentId | coinbase-link>
42118
+ npx @rozoai/checkout receipt <rozoPaymentId | coinbase-link>
42090
42119
 
42091
42120
  COINS (--with)
42092
42121
  usdt-solana usdc-solana usdt-bnb usdc-bnb
@@ -42139,6 +42168,17 @@ EXAMPLES
42139
42168
  npx @rozoai/checkout pay pl_01... --with usdt-solana --email you@example.com
42140
42169
  npx @rozoai/checkout pay --bitrefill-invoice 8f3k2 --to 0xAbC\u2026123 --amount 7.90 --expires-at 2026-10-01T12:00:00Z --with usdc-stellar
42141
42170
  npx @rozoai/checkout status 11111111-2222-4333-8444-555555555555
42171
+ npx @rozoai/checkout receipt 11111111-2222-4333-8444-555555555555 --json
42172
+
42173
+ RECEIPT
42174
+ receipt reads the order once (nothing is created or sent) and reports three
42175
+ layers separately: did your money reach Rozo, was the merchant invoice paid,
42176
+ and was the service delivered (always "unknown": Rozo cannot see OpenRouter
42177
+ credits). Exit 0 only when the merchant invoice is settled; 3 when it is
42178
+ still in flight or unknown; 1 when it expired unpaid or needs a human.
42179
+ In --json output from status and receipt, read paymentOutcome and
42180
+ nextAction. The older "success" field means the query worked, not that the
42181
+ invoice is paid.
42142
42182
 
42143
42183
  Creating an order moves no money; an unfunded order simply expires. Nothing is
42144
42184
  paid until you confirm, and --send is required before anything is signed.
@@ -50830,6 +50870,12 @@ function formatUnits(value, decimals) {
50830
50870
  return format(value, decimals);
50831
50871
  }
50832
50872
 
50873
+ // node_modules/viem/_esm/utils/unit/parseGwei.js
50874
+ init_Value();
50875
+ function parseGwei(ether, unit = "wei") {
50876
+ return fromGwei(ether, unit);
50877
+ }
50878
+
50833
50879
  // node_modules/viem/_esm/utils/unit/parseUnits.js
50834
50880
  init_Value();
50835
50881
  function parseUnits(value, decimals) {
@@ -50973,7 +51019,7 @@ init_createBatchScheduler();
50973
51019
  init_stringify();
50974
51020
  async function multicall(client, parameters) {
50975
51021
  const { account, authorizationList, allowFailure = true, blockHash, blockNumber, blockOverrides, blockTag, requireCanonical, stateOverride } = parameters;
50976
- const contracts = parameters.contracts;
51022
+ const contracts2 = parameters.contracts;
50977
51023
  const batch = typeof client.batch?.multicall === "object" ? client.batch.multicall : {};
50978
51024
  const batchSize = parameters.batchSize ?? batch.batchSize ?? 1024;
50979
51025
  const deployless = parameters.deployless ?? batch.deployless ?? false;
@@ -50994,8 +51040,8 @@ async function multicall(client, parameters) {
50994
51040
  const chunkedCalls = [[]];
50995
51041
  let currentChunk = 0;
50996
51042
  let currentChunkSize = 0;
50997
- for (let i = 0; i < contracts.length; i++) {
50998
- const { abi: abi2, address, args, functionName } = contracts[i];
51043
+ for (let i = 0; i < contracts2.length; i++) {
51044
+ const { abi: abi2, address, args, functionName } = contracts2[i];
50999
51045
  try {
51000
51046
  const callData = encodeFunctionData({ abi: abi2, args, functionName });
51001
51047
  currentChunkSize += (callData.length - 2) / 2;
@@ -51089,7 +51135,7 @@ async function multicall(client, parameters) {
51089
51135
  for (let j = 0; j < aggregate3Result.length; j++) {
51090
51136
  const { returnData, success } = aggregate3Result[j];
51091
51137
  const { callData } = batches[i][j];
51092
- const { abi: abi2, address, functionName, args } = contracts[results.length];
51138
+ const { abi: abi2, address, functionName, args } = contracts2[results.length];
51093
51139
  try {
51094
51140
  if (callData === "0x")
51095
51141
  throw new AbiDecodingZeroDataError();
@@ -51116,7 +51162,7 @@ async function multicall(client, parameters) {
51116
51162
  }
51117
51163
  }
51118
51164
  }
51119
- if (results.length !== contracts.length)
51165
+ if (results.length !== contracts2.length)
51120
51166
  throw new BaseError2("multicall results mismatch");
51121
51167
  return results;
51122
51168
  }
@@ -54961,9 +55007,6 @@ function classifyStatus({
54961
55007
  { escalate: true }
54962
55008
  );
54963
55009
  }
54964
- if (!bitrefill && (routerStatus === "paid" || coinbase?.settled === true)) {
54965
- return mk("settled", "Coinbase invoice settled by the funder wallet.", { terminal: true });
54966
- }
54967
55010
  if (routerStatus === "failed_pay_invoice" || routerStatus === "failed_insufficient_balance") {
54968
55011
  return mk(
54969
55012
  "stuck_after_payment",
@@ -54971,6 +55014,9 @@ function classifyStatus({
54971
55014
  { terminal: false, escalate: true }
54972
55015
  );
54973
55016
  }
55017
+ if (!bitrefill && (routerStatus === "paid" || coinbase?.settled === true)) {
55018
+ return mk("settled", "Coinbase invoice settled by the funder wallet.", { terminal: true });
55019
+ }
54974
55020
  if (moneyDetected && receipt && receipt.state === "underpaid") {
54975
55021
  return mk(
54976
55022
  "underpaid",
@@ -55228,6 +55274,17 @@ async function main2(argv) {
55228
55274
  expiresAt: created.expiresAt ?? null,
55229
55275
  expiresIn: Number.isFinite(remainingMs) ? formatRemaining(remainingMs) : null
55230
55276
  },
55277
+ // Machine-readable form of the guidance below. The live order keeps
55278
+ // its own coin; a different coin is only possible once it has
55279
+ // expired unfunded (then pay on the same link creates a new order).
55280
+ nextAction: {
55281
+ type: "choose_method",
55282
+ canRetryQuery: true,
55283
+ canCreateOrder: false,
55284
+ canSend: false,
55285
+ payExistingWith: { chainId: existing.chainId ?? null, tokenSymbol: existing.tokenSymbol ?? null },
55286
+ switchPossibleAfter: created.expiresAt ?? null
55287
+ },
55231
55288
  guidance: `Either pay the existing order with ${existing.tokenSymbol ?? "its own coin"} (re-run with --chain ${existing.chainId} --token ${existing.tokenSymbol}), or wait ${Number.isFinite(remainingMs) ? formatRemaining(remainingMs) : "for it to expire"} for it to expire and then create a new one. Unpaid orders cost nothing.`
55232
55289
  },
55233
55290
  EXIT_ERROR
@@ -55841,6 +55898,241 @@ async function run3(argv = process.argv.slice(2)) {
55841
55898
  return main3(argv);
55842
55899
  }
55843
55900
 
55901
+ // scripts/src/lib/receipt.mjs
55902
+ var SCHEMA_VERSION = 1;
55903
+ var PAYMENT_OUTCOMES = Object.freeze([
55904
+ "awaiting_payment",
55905
+ "processing",
55906
+ "settled",
55907
+ "expired_unfunded",
55908
+ "needs_attention",
55909
+ "unknown"
55910
+ ]);
55911
+ var NEXT_ACTION_TYPES = Object.freeze([
55912
+ "pay",
55913
+ "check_status",
55914
+ "choose_method",
55915
+ "request_new_invoice",
55916
+ "contact_support",
55917
+ "none"
55918
+ ]);
55919
+ var STATE_TO_OUTCOME = Object.freeze({
55920
+ awaiting_deposit: "awaiting_payment",
55921
+ payin_detected: "processing",
55922
+ payin_confirmed: "processing",
55923
+ bridging: "processing",
55924
+ paying_coinbase: "processing",
55925
+ settled: "settled",
55926
+ expired_unfunded: "expired_unfunded",
55927
+ invoice_expired: "expired_unfunded",
55928
+ deadline_unknown: "needs_attention",
55929
+ underpaid: "needs_attention",
55930
+ stuck_after_payment: "needs_attention",
55931
+ unknown: "unknown"
55932
+ });
55933
+ var SERVICE_DELIVERY_NOTE = {
55934
+ coinbase: "The invoice is paid. Arrival of the OpenRouter credits (or other goods) is not independently verified by Rozo; check the merchant account.",
55935
+ bitrefill: "USDC reached the Bitrefill invoice address. Delivery of the Bitrefill order is not independently verified by Rozo; check the Bitrefill order page."
55936
+ };
55937
+ function merchantSettlementProved(result) {
55938
+ if (result?.provider === "bitrefill") return result?.state === "settled";
55939
+ return result?.backend?.coinbaseSettled === true;
55940
+ }
55941
+ function hasLocalSend(result) {
55942
+ return Boolean(result?.localSend && result.localSend.status);
55943
+ }
55944
+ function paymentOutcomeFor(result) {
55945
+ if (!result) return "unknown";
55946
+ if (result.escalate) return "needs_attention";
55947
+ if (result.unknown) return "unknown";
55948
+ let outcome = Object.prototype.hasOwnProperty.call(STATE_TO_OUTCOME, result.state) ? STATE_TO_OUTCOME[result.state] : "unknown";
55949
+ if (outcome === "settled" && !merchantSettlementProved(result)) outcome = "processing";
55950
+ if (outcome === "settled" && result.provider !== "bitrefill") {
55951
+ const router = result.backend?.routerStatus ?? null;
55952
+ if (router && router.startsWith("failed_")) return "needs_attention";
55953
+ if (router && router !== "paid") outcome = "processing";
55954
+ }
55955
+ if (!result.authoritativeView && (outcome === "awaiting_payment" || outcome === "expired_unfunded")) {
55956
+ return "unknown";
55957
+ }
55958
+ if ((result.moneyDetected || hasLocalSend(result)) && outcome === "expired_unfunded") {
55959
+ return "needs_attention";
55960
+ }
55961
+ if (result.moneyDetected && outcome === "awaiting_payment") return "processing";
55962
+ return outcome;
55963
+ }
55964
+ function nextActionFor(result, outcome = paymentOutcomeFor(result)) {
55965
+ const locked = Boolean(result?.moneyDetected) || hasLocalSend(result);
55966
+ const base = { canRetryQuery: true, canCreateOrder: false, canSend: false };
55967
+ const bitrefill = result?.provider === "bitrefill";
55968
+ switch (outcome) {
55969
+ case "awaiting_payment":
55970
+ if (locked) {
55971
+ return {
55972
+ ...base,
55973
+ type: "check_status",
55974
+ message: "A send was already recorded for this order. Do NOT pay again; poll status until the pay-in shows up."
55975
+ };
55976
+ }
55977
+ if (!result?.sendWindow?.ok) {
55978
+ const code = result?.sendWindow?.code ?? "SEND_WINDOW_UNKNOWN";
55979
+ if (code === "LINK_NO_LONGER_PAYABLE") {
55980
+ return {
55981
+ ...base,
55982
+ type: "request_new_invoice",
55983
+ reason: code,
55984
+ message: "The merchant link no longer accepts payment. Do NOT fund this order; get a new payment link."
55985
+ };
55986
+ }
55987
+ if (code === "EXPIRED" || code === "EXPIRY_MARGIN") {
55988
+ return {
55989
+ ...base,
55990
+ type: "check_status",
55991
+ reason: code,
55992
+ message: "Too little time is left to pay this order safely. Do NOT fund it. Once it shows expired_unfunded, run pay again on the same link for a new order."
55993
+ };
55994
+ }
55995
+ return {
55996
+ ...base,
55997
+ type: "check_status",
55998
+ reason: code,
55999
+ message: "Whether this order can still be paid could not be proved. Do NOT fund it on a guess; retry."
56000
+ };
56001
+ }
56002
+ return {
56003
+ ...base,
56004
+ type: "pay",
56005
+ canSend: true,
56006
+ message: "Send the exact amount from the order deposit block, once."
56007
+ };
56008
+ case "processing":
56009
+ return {
56010
+ ...base,
56011
+ type: "check_status",
56012
+ message: "Funds are moving. Do NOT pay again; poll status."
56013
+ };
56014
+ case "settled":
56015
+ return { ...base, type: "none", canRetryQuery: false, message: "Nothing to do." };
56016
+ case "expired_unfunded":
56017
+ if (bitrefill) {
56018
+ return {
56019
+ ...base,
56020
+ type: "request_new_invoice",
56021
+ message: "Nothing was funded. Create a fresh Bitrefill invoice and pay that one."
56022
+ };
56023
+ }
56024
+ return {
56025
+ ...base,
56026
+ type: "choose_method",
56027
+ canCreateOrder: true,
56028
+ message: "Nothing arrived for this order. If your own wallet shows no send to the old deposit address and no pending Lightning payment, run pay again on the same link: it creates a new order, and you may pick a different coin. Never fund the old deposit address."
56029
+ };
56030
+ case "needs_attention":
56031
+ if (result?.state === "deadline_unknown" && !locked) {
56032
+ return {
56033
+ ...base,
56034
+ type: "request_new_invoice",
56035
+ message: "The invoice deadline is unknown here, so the order is not payable. Re-run pay with the invoice details, or create a fresh invoice."
56036
+ };
56037
+ }
56038
+ return {
56039
+ ...base,
56040
+ type: "contact_support",
56041
+ message: "Do NOT pay again and do NOT create a new order. Keep linkId, rozoPaymentId and every tx hash, and contact Rozo support."
56042
+ };
56043
+ default:
56044
+ return {
56045
+ ...base,
56046
+ type: "check_status",
56047
+ message: "The order state could not be established. This is not evidence that nothing was paid. Retry with the rozoPaymentId before doing anything else."
56048
+ };
56049
+ }
56050
+ }
56051
+ function sourcePaymentLayer(result) {
56052
+ const payin = result?.payin || {};
56053
+ let status;
56054
+ if (result?.unknown || !result?.authoritativeView) status = "unknown";
56055
+ else if (payin.confirmedAt) status = "confirmed";
56056
+ else if (result?.moneyDetected) status = "detected";
56057
+ else status = "none";
56058
+ return {
56059
+ status,
56060
+ evidence: status === "unknown" ? null : "rozo-intents",
56061
+ chain: payin.chain ?? null,
56062
+ expected: payin.expected ?? null,
56063
+ received: payin.received ?? null,
56064
+ txHash: payin.txHash ?? null,
56065
+ confirmedAt: payin.confirmedAt ?? null,
56066
+ localSend: result?.localSend ? {
56067
+ status: result.localSend.status,
56068
+ txHash: result.localSend.txHash ?? null,
56069
+ claimedAt: result.localSend.claimedAt ?? null,
56070
+ // A wallet broadcast proves the payer sent, not that Rozo received.
56071
+ evidence: "local-wallet"
56072
+ } : null
56073
+ };
56074
+ }
56075
+ function merchantSettlementLayer(result) {
56076
+ if (result?.provider === "bitrefill") {
56077
+ const proved = result?.state === "settled";
56078
+ return {
56079
+ status: proved ? "confirmed" : result?.unknown ? "unknown" : "pending",
56080
+ evidence: proved ? "rozo-payout" : null,
56081
+ provider: "bitrefill",
56082
+ txHash: result?.payout?.txHash ?? null,
56083
+ confirmedAt: result?.payout?.confirmedAt ?? null
56084
+ };
56085
+ }
56086
+ const backend = result?.backend || {};
56087
+ let status;
56088
+ if (backend.routerStatus && backend.routerStatus.startsWith("failed_")) status = "unknown";
56089
+ else if (backend.coinbaseSettled === true) status = "confirmed";
56090
+ else if (backend.coinbaseSettled === null || backend.coinbaseSettled === void 0) {
56091
+ status = backend.routerStatus === "paid" || result?.unknown ? "unknown" : "pending";
56092
+ } else status = "pending";
56093
+ return {
56094
+ status,
56095
+ evidence: status === "confirmed" ? "coinbase" : null,
56096
+ provider: "coinbase",
56097
+ coinbaseStatus: backend.coinbaseStatus ?? null,
56098
+ routerStatus: backend.routerStatus ?? null,
56099
+ // The router reporting `paid` is recorded, but it is not settlement proof.
56100
+ routerReportsPaid: backend.routerStatus === "paid",
56101
+ // Coinbase may read settled because someone else paid the link.
56102
+ routerFailed: Boolean(backend.routerStatus && backend.routerStatus.startsWith("failed_")),
56103
+ payoutTxHash: result?.payout?.txHash ?? null
56104
+ };
56105
+ }
56106
+ function serviceDeliveryLayer(result, merchant) {
56107
+ return {
56108
+ status: "unknown",
56109
+ evidence: null,
56110
+ note: merchant.status === "confirmed" ? SERVICE_DELIVERY_NOTE[result?.provider === "bitrefill" ? "bitrefill" : "coinbase"] : "Not applicable until the merchant invoice is settled."
56111
+ };
56112
+ }
56113
+ function buildReceipt(result, { observedAt = (/* @__PURE__ */ new Date()).toISOString() } = {}) {
56114
+ const paymentOutcome = paymentOutcomeFor(result);
56115
+ const merchantSettlement = merchantSettlementLayer(result);
56116
+ return {
56117
+ schemaVersion: SCHEMA_VERSION,
56118
+ orderId: result?.rozoPaymentId ?? null,
56119
+ linkId: result?.linkId ?? null,
56120
+ provider: result?.provider ?? null,
56121
+ observedAt,
56122
+ paymentOutcome,
56123
+ state: result?.state ?? "unknown",
56124
+ sourcePayment: sourcePaymentLayer(result),
56125
+ merchantSettlement,
56126
+ serviceDelivery: serviceDeliveryLayer(result, merchantSettlement),
56127
+ nextAction: nextActionFor(result, paymentOutcome)
56128
+ };
56129
+ }
56130
+ function receiptExitCode(outcome) {
56131
+ if (outcome === "settled") return 0;
56132
+ if (outcome === "expired_unfunded" || outcome === "needs_attention") return 1;
56133
+ return 3;
56134
+ }
56135
+
55844
56136
  // scripts/src/status.mjs
55845
56137
  var POLL_INTERVAL_MS = 1e4;
55846
56138
  var sleep = (ms) => new Promise((r) => setTimeout(r, ms));
@@ -55855,6 +56147,42 @@ function resolveProvider(explicit, rozoPaymentId) {
55855
56147
  }
55856
56148
  return null;
55857
56149
  }
56150
+ function localSendFor(id, read = readState) {
56151
+ if (!id || !isRozoPaymentId(id)) return null;
56152
+ let state;
56153
+ try {
56154
+ state = read(id);
56155
+ } catch (err) {
56156
+ return { status: "unreadable", txHash: null, claimedAt: null, error: err?.code ?? "STATE_UNREADABLE" };
56157
+ }
56158
+ const send = state?.send;
56159
+ if (!send || !send.status) return null;
56160
+ return { status: send.status, txHash: publicHash(send.txHash ?? null), claimedAt: send.claimedAt ?? null };
56161
+ }
56162
+ function sendWindowFor({ provider, chainId, payment, status, bitrefillExpiry, now = Date.now() }) {
56163
+ if (provider === "bitrefill") {
56164
+ const deadline = bitrefillExpiry ?? null;
56165
+ const expiry2 = checkExpiry({
56166
+ now,
56167
+ chainId,
56168
+ intentExpiresAt: payment?.expiresAt ?? deadline,
56169
+ coinbaseExpiry: deadline
56170
+ });
56171
+ return expiry2.ok ? { ok: true, code: null, minutesOfSlack: Math.floor(expiry2.msOfSlack / 6e4) } : { ok: false, code: expiry2.code, reason: expiry2.reason };
56172
+ }
56173
+ if (!status) {
56174
+ return { ok: false, code: "LINK_PAYABILITY_UNKNOWN", reason: "The Coinbase link state could not be read." };
56175
+ }
56176
+ const payable = checkPayable(status, now);
56177
+ if (!payable.ok) return { ok: false, code: payable.code, reason: payable.reason };
56178
+ const expiry = checkExpiry({
56179
+ now,
56180
+ chainId,
56181
+ intentExpiresAt: payment?.expiresAt ?? status?.rozoPayment?.expiresAt,
56182
+ coinbaseExpiry: status?.coinbase?.preApprovalExpiry
56183
+ });
56184
+ return expiry.ok ? { ok: true, code: null, minutesOfSlack: Math.floor(expiry.msOfSlack / 6e4) } : { ok: false, code: expiry.code, reason: expiry.reason };
56185
+ }
55858
56186
  async function snapshot({ rozoPaymentId, linkId, provider: explicitProvider }) {
55859
56187
  let provider = resolveProvider(explicitProvider, rozoPaymentId);
55860
56188
  let prefetched = null;
@@ -55952,7 +56280,7 @@ async function snapshot({ rozoPaymentId, linkId, provider: explicitProvider }) {
55952
56280
  expected: source.amount ? formatAmount(source) : null,
55953
56281
  received: source.amountReceived ?? null,
55954
56282
  receipt: verdict.receipt,
55955
- txHash: source.txHash ?? null,
56283
+ txHash: publicHash(source.txHash ?? null),
55956
56284
  confirmedAt: source.confirmedAt ?? null,
55957
56285
  senderAddressMasked: source.senderAddress ? maskAddress2(source.senderAddress) : null,
55958
56286
  chain: source.chainId ? chainName(source.chainId) : null
@@ -55971,9 +56299,11 @@ async function snapshot({ rozoPaymentId, linkId, provider: explicitProvider }) {
55971
56299
  };
55972
56300
  })(),
55973
56301
  payout: {
55974
- txHash: payment?.destination?.txHash ?? status?.rozoPayment?.destination?.txHash ?? null,
56302
+ txHash: publicHash(payment?.destination?.txHash ?? status?.rozoPayment?.destination?.txHash ?? null),
55975
56303
  confirmedAt: payment?.destination?.confirmedAt ?? status?.rozoPayment?.destination?.confirmedAt ?? null
55976
56304
  },
56305
+ localSend: localSendFor(id),
56306
+ sendWindow: state === "awaiting_deposit" ? sendWindowFor({ provider, chainId: source.chainId, payment, status, bitrefillExpiry }) : null,
55977
56307
  errors: [statusError, paymentError].filter(Boolean)
55978
56308
  };
55979
56309
  }
@@ -56022,9 +56352,16 @@ async function main4(argv) {
56022
56352
  const guidance = statusGuidance(result);
56023
56353
  const unresolved = watch && !result.terminal && !result.escalate && !result.unknown;
56024
56354
  const failed = result.escalate || result.unknown || !result.authoritativeView;
56355
+ const paymentOutcome = paymentOutcomeFor(result);
56025
56356
  emit(
56026
56357
  {
56358
+ // `success` keeps its historical meaning (the query produced a clean
56359
+ // answer). It is NOT "the invoice is paid": read paymentOutcome.
56027
56360
  success: !failed,
56361
+ schemaVersion: SCHEMA_VERSION,
56362
+ operationOk: !result.unknown,
56363
+ paymentOutcome,
56364
+ nextAction: nextActionFor(result, paymentOutcome),
56028
56365
  step: "status",
56029
56366
  ...result,
56030
56367
  history,
@@ -56040,6 +56377,40 @@ async function run4(argv = process.argv.slice(2)) {
56040
56377
  return main4(argv);
56041
56378
  }
56042
56379
 
56380
+ // scripts/src/receipt.mjs
56381
+ async function main5(argv) {
56382
+ const args = parseArgs(argv);
56383
+ const rozoPaymentId = args["rozo-payment-id"] || (isRozoPaymentId(args._[0]) ? args._[0] : null);
56384
+ const linkId = args["link-id"] || (!rozoPaymentId ? args._[0] : null);
56385
+ const provider = args.provider ? String(args.provider) : null;
56386
+ if (provider && provider !== "bitrefill" && provider !== "coinbase") {
56387
+ usage("--provider must be coinbase or bitrefill");
56388
+ }
56389
+ if (provider === "bitrefill" && !rozoPaymentId) {
56390
+ usage("A Bitrefill order is tracked by --rozo-payment-id <uuid>.");
56391
+ }
56392
+ if (!rozoPaymentId && !linkId) {
56393
+ usage("Required: --rozo-payment-id <uuid> and/or --link-id <pl_* | paymentSession_*>");
56394
+ }
56395
+ const result = await snapshot({ rozoPaymentId, linkId, provider });
56396
+ const receipt = buildReceipt(result);
56397
+ const exitCode = receiptExitCode(receipt.paymentOutcome);
56398
+ emit(
56399
+ {
56400
+ success: receipt.paymentOutcome === "settled",
56401
+ operationOk: !result.unknown,
56402
+ step: "receipt",
56403
+ receipt,
56404
+ errors: result.errors,
56405
+ ...exitCode === 0 ? {} : { support: SUPPORT }
56406
+ },
56407
+ exitCode
56408
+ );
56409
+ }
56410
+ async function run5(argv = process.argv.slice(2)) {
56411
+ return main5(argv);
56412
+ }
56413
+
56043
56414
  // scripts/src/lib/passphrase.mjs
56044
56415
  import readline from "node:readline";
56045
56416
  function promptPassphrase(prompt = " Keystore passphrase: ") {
@@ -56279,6 +56650,290 @@ function broadcastOutcome({ receiptStatus, executionError = null, receiptSeen =
56279
56650
  };
56280
56651
  }
56281
56652
 
56653
+ // scripts/src/lib/gas.mjs
56654
+ var EVM_FEE_COIN = Object.freeze({
56655
+ 1: { symbol: "ETH", network: "Ethereum" },
56656
+ 56: { symbol: "BNB", network: "BNB Chain" },
56657
+ 137: { symbol: "POL", network: "Polygon" },
56658
+ 8453: { symbol: "ETH", network: "Base" }
56659
+ });
56660
+ var DEFAULT_GAS_BUFFER_PCT = 20;
56661
+ var MAX_GAS_BUFFER_PCT = 200;
56662
+ function gasBufferPct(env = process.env) {
56663
+ const raw = env?.ROZO_CHECKOUT_GAS_BUFFER_PCT;
56664
+ if (raw === void 0 || raw === "") return DEFAULT_GAS_BUFFER_PCT;
56665
+ const n = Number(raw);
56666
+ if (!Number.isInteger(n) || n < 0 || n > MAX_GAS_BUFFER_PCT) return DEFAULT_GAS_BUFFER_PCT;
56667
+ return n;
56668
+ }
56669
+ function formatAtomic(atomic, decimals) {
56670
+ const neg = atomic < 0n;
56671
+ const v = neg ? -atomic : atomic;
56672
+ const base = 10n ** BigInt(decimals);
56673
+ const whole = v / base;
56674
+ const frac = (v % base).toString().padStart(decimals, "0").replace(/0+$/, "");
56675
+ return `${neg ? "-" : ""}${whole}${frac ? `.${frac}` : ""}`;
56676
+ }
56677
+ function evmFeeRequirement({ gasEstimate, maxFeePerGas, gasPrice, bufferPct = DEFAULT_GAS_BUFFER_PCT }) {
56678
+ const perGas = typeof maxFeePerGas === "bigint" ? maxFeePerGas : typeof gasPrice === "bigint" ? gasPrice : null;
56679
+ if (typeof gasEstimate !== "bigint" || perGas === null) return null;
56680
+ const gasLimit = (gasEstimate * BigInt(100 + bufferPct) + 99n) / 100n;
56681
+ return { gasLimit, perGas, pricing: typeof maxFeePerGas === "bigint" ? "eip1559" : "legacy", fee: gasLimit * perGas };
56682
+ }
56683
+ function assessFee({ balance, required, decimals, symbol, network }) {
56684
+ const ok = required === null ? balance > 0n : balance >= required;
56685
+ const details = {
56686
+ network,
56687
+ feeCoin: symbol,
56688
+ balance: formatAtomic(balance, decimals),
56689
+ required: required === null ? null : formatAtomic(required, decimals),
56690
+ shortfall: required === null || ok ? null : formatAtomic(required - balance, decimals)
56691
+ };
56692
+ return { ok, details };
56693
+ }
56694
+ function insufficient(details, extra) {
56695
+ const need = details.required ? `about ${details.required} ${details.feeCoin}` : `some ${details.feeCoin}`;
56696
+ return new SkillError(
56697
+ "INSUFFICIENT_GAS",
56698
+ `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}` : ""),
56699
+ details
56700
+ );
56701
+ }
56702
+ function unavailable(network, err) {
56703
+ return new SkillError(
56704
+ "GAS_CHECK_UNAVAILABLE",
56705
+ `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.`,
56706
+ { network }
56707
+ );
56708
+ }
56709
+ async function estimateFees(client) {
56710
+ try {
56711
+ return await client.estimateFeesPerGas();
56712
+ } catch (err) {
56713
+ if (err?.name !== "Eip1559FeesNotSupportedError") throw err;
56714
+ return client.estimateFeesPerGas({ type: "legacy" });
56715
+ }
56716
+ }
56717
+ var OP_STACK_GAS_PRICE_ORACLE = "0x420000000000000000000000000000000000000F";
56718
+ var OP_STACK_CHAIN_IDS = /* @__PURE__ */ new Set([8453]);
56719
+ async function evmGasPrecheck({
56720
+ client,
56721
+ chainId,
56722
+ from: from15,
56723
+ to,
56724
+ data,
56725
+ value = 0n,
56726
+ bufferPct = gasBufferPct(),
56727
+ estimateL1Fee: estimateL1Fee2 = null
56728
+ }) {
56729
+ const coin = EVM_FEE_COIN[Number(chainId)] ?? { symbol: "native coin", network: `chain ${chainId}` };
56730
+ let balance;
56731
+ try {
56732
+ balance = await client.getBalance({ address: from15 });
56733
+ } catch (err) {
56734
+ throw unavailable(coin.network, err);
56735
+ }
56736
+ let gasEstimate;
56737
+ let fees;
56738
+ try {
56739
+ gasEstimate = await client.estimateGas({ account: from15, to, data, value });
56740
+ fees = await estimateFees(client);
56741
+ } catch (err) {
56742
+ if (balance === 0n) {
56743
+ throw insufficient(
56744
+ assessFee({ balance, required: null, decimals: 18, symbol: coin.symbol, network: coin.network }).details
56745
+ );
56746
+ }
56747
+ throw unavailable(coin.network, err);
56748
+ }
56749
+ const req = evmFeeRequirement({
56750
+ gasEstimate,
56751
+ maxFeePerGas: fees?.maxFeePerGas,
56752
+ gasPrice: fees?.gasPrice,
56753
+ bufferPct
56754
+ });
56755
+ if (!req) throw unavailable(coin.network, new Error("the node returned no fee price"));
56756
+ let l1Fee = 0n;
56757
+ if (OP_STACK_CHAIN_IDS.has(Number(chainId))) {
56758
+ if (typeof estimateL1Fee2 !== "function") {
56759
+ throw unavailable(coin.network, new Error("no L1 data fee estimator for this OP-stack chain"));
56760
+ }
56761
+ try {
56762
+ l1Fee = (await estimateL1Fee2() * BigInt(100 + bufferPct) + 99n) / 100n;
56763
+ } catch (err) {
56764
+ throw unavailable(coin.network, err);
56765
+ }
56766
+ }
56767
+ const required = req.fee + l1Fee + value;
56768
+ const { ok, details } = assessFee({ balance, required, decimals: 18, symbol: coin.symbol, network: coin.network });
56769
+ const full = {
56770
+ ...details,
56771
+ gasLimit: req.gasLimit.toString(),
56772
+ pricing: req.pricing,
56773
+ bufferPct,
56774
+ l1DataFee: OP_STACK_CHAIN_IDS.has(Number(chainId)) ? formatAtomic(l1Fee, 18) : null
56775
+ };
56776
+ if (!ok) throw insufficient(full);
56777
+ return full;
56778
+ }
56779
+ var LAMPORTS_DECIMALS = 9;
56780
+ async function solFeePrecheck({ connection, payer, message }) {
56781
+ const network = "Solana";
56782
+ let balance;
56783
+ let fee;
56784
+ let rentMin;
56785
+ try {
56786
+ balance = BigInt(await connection.getBalance(payer, "confirmed"));
56787
+ const res = await connection.getFeeForMessage(message, "confirmed");
56788
+ if (res?.value === null || res?.value === void 0) {
56789
+ throw new Error("the node returned no fee for this transaction (stale blockhash?)");
56790
+ }
56791
+ fee = BigInt(res.value);
56792
+ rentMin = BigInt(await connection.getMinimumBalanceForRentExemption(0));
56793
+ } catch (err) {
56794
+ if (balance === 0n) {
56795
+ throw insufficient(
56796
+ assessFee({ balance, required: null, decimals: LAMPORTS_DECIMALS, symbol: "SOL", network }).details
56797
+ );
56798
+ }
56799
+ throw unavailable(network, err);
56800
+ }
56801
+ const remaining = balance - fee;
56802
+ const required = remaining === 0n ? fee : fee + rentMin;
56803
+ const { ok, details } = assessFee({ balance, required, decimals: LAMPORTS_DECIMALS, symbol: "SOL", network });
56804
+ const full = { ...details, fee: formatAtomic(fee, LAMPORTS_DECIMALS), rentExemptMinimum: formatAtomic(rentMin, LAMPORTS_DECIMALS) };
56805
+ if (!ok) {
56806
+ throw insufficient(
56807
+ full,
56808
+ balance >= fee ? "Solana also requires a wallet to keep a small minimum balance after paying the fee." : null
56809
+ );
56810
+ }
56811
+ return full;
56812
+ }
56813
+
56814
+ // node_modules/viem/_esm/op-stack/contracts.js
56815
+ var contracts = {
56816
+ gasPriceOracle: { address: "0x420000000000000000000000000000000000000F" },
56817
+ l1Block: { address: "0x4200000000000000000000000000000000000015" },
56818
+ l2CrossDomainMessenger: {
56819
+ address: "0x4200000000000000000000000000000000000007"
56820
+ },
56821
+ l2Erc721Bridge: { address: "0x4200000000000000000000000000000000000014" },
56822
+ l2StandardBridge: { address: "0x4200000000000000000000000000000000000010" },
56823
+ l2ToL1MessagePasser: {
56824
+ address: "0x4200000000000000000000000000000000000016"
56825
+ }
56826
+ };
56827
+
56828
+ // node_modules/viem/_esm/op-stack/abis.js
56829
+ var gasPriceOracleAbi = [
56830
+ { inputs: [], stateMutability: "nonpayable", type: "constructor" },
56831
+ {
56832
+ inputs: [],
56833
+ name: "DECIMALS",
56834
+ outputs: [{ internalType: "uint256", name: "", type: "uint256" }],
56835
+ stateMutability: "view",
56836
+ type: "function"
56837
+ },
56838
+ {
56839
+ inputs: [],
56840
+ name: "baseFee",
56841
+ outputs: [{ internalType: "uint256", name: "", type: "uint256" }],
56842
+ stateMutability: "view",
56843
+ type: "function"
56844
+ },
56845
+ {
56846
+ inputs: [],
56847
+ name: "decimals",
56848
+ outputs: [{ internalType: "uint256", name: "", type: "uint256" }],
56849
+ stateMutability: "pure",
56850
+ type: "function"
56851
+ },
56852
+ {
56853
+ inputs: [],
56854
+ name: "gasPrice",
56855
+ outputs: [{ internalType: "uint256", name: "", type: "uint256" }],
56856
+ stateMutability: "view",
56857
+ type: "function"
56858
+ },
56859
+ {
56860
+ inputs: [{ internalType: "bytes", name: "_data", type: "bytes" }],
56861
+ name: "getL1Fee",
56862
+ outputs: [{ internalType: "uint256", name: "", type: "uint256" }],
56863
+ stateMutability: "view",
56864
+ type: "function"
56865
+ },
56866
+ {
56867
+ inputs: [{ internalType: "bytes", name: "_data", type: "bytes" }],
56868
+ name: "getL1GasUsed",
56869
+ outputs: [{ internalType: "uint256", name: "", type: "uint256" }],
56870
+ stateMutability: "view",
56871
+ type: "function"
56872
+ },
56873
+ {
56874
+ inputs: [],
56875
+ name: "l1BaseFee",
56876
+ outputs: [{ internalType: "uint256", name: "", type: "uint256" }],
56877
+ stateMutability: "view",
56878
+ type: "function"
56879
+ },
56880
+ {
56881
+ inputs: [],
56882
+ name: "overhead",
56883
+ outputs: [{ internalType: "uint256", name: "", type: "uint256" }],
56884
+ stateMutability: "view",
56885
+ type: "function"
56886
+ },
56887
+ {
56888
+ inputs: [],
56889
+ name: "scalar",
56890
+ outputs: [{ internalType: "uint256", name: "", type: "uint256" }],
56891
+ stateMutability: "view",
56892
+ type: "function"
56893
+ },
56894
+ {
56895
+ inputs: [],
56896
+ name: "version",
56897
+ outputs: [{ internalType: "string", name: "", type: "string" }],
56898
+ stateMutability: "view",
56899
+ type: "function"
56900
+ }
56901
+ ];
56902
+
56903
+ // node_modules/viem/_esm/op-stack/actions/estimateL1Fee.js
56904
+ init_getChainContractAddress();
56905
+ async function estimateL1Fee(client, args) {
56906
+ const { chain = client.chain, gasPriceOracleAddress: gasPriceOracleAddress_ } = args;
56907
+ const gasPriceOracleAddress = (() => {
56908
+ if (gasPriceOracleAddress_)
56909
+ return gasPriceOracleAddress_;
56910
+ if (chain)
56911
+ return getChainContractAddress({
56912
+ chain,
56913
+ contract: "gasPriceOracle"
56914
+ });
56915
+ return contracts.gasPriceOracle.address;
56916
+ })();
56917
+ const transaction = serializeTransaction({
56918
+ ...args,
56919
+ chainId: chain?.id ?? 1,
56920
+ type: "eip1559",
56921
+ // Set upper-limit-ish stub values. Shouldn't affect the estimate too much as we are
56922
+ // tweaking dust bytes here (as opposed to long `data` bytes).
56923
+ // See: https://github.com/ethereum-optimism/optimism/blob/54d02df55523c9e1b4b38ed082c12a42087323a0/packages/contracts-bedrock/src/L2/GasPriceOracle.sol#L242-L248.
56924
+ gas: args.data ? 300000n : 21000n,
56925
+ maxFeePerGas: parseGwei("5"),
56926
+ maxPriorityFeePerGas: parseGwei("1"),
56927
+ nonce: 1
56928
+ });
56929
+ return readContract(client, {
56930
+ abi: gasPriceOracleAbi,
56931
+ address: gasPriceOracleAddress,
56932
+ functionName: "getL1Fee",
56933
+ args: [transaction]
56934
+ });
56935
+ }
56936
+
56282
56937
  // node_modules/viem/_esm/accounts/privateKeyToAccount.js
56283
56938
  init_secp256k1();
56284
56939
  init_toHex();
@@ -56472,7 +57127,7 @@ function chainDef(chainId, rpcUrl) {
56472
57127
  rpcUrls: { default: { http: [rpcUrl] } }
56473
57128
  });
56474
57129
  }
56475
- async function main5(argv) {
57130
+ async function main6(argv) {
56476
57131
  const args = parseArgs(argv);
56477
57132
  const rozoPaymentId = assertRozoPaymentId(args["rozo-payment-id"] || args._[0]);
56478
57133
  assertNoTrackedDotEnv();
@@ -56550,6 +57205,25 @@ async function main5(argv) {
56550
57205
  `Sender holds ${balance} atomic units of ${source.tokenSymbol} on ${chainName(chainId)}; ${amountAtomic} are required.`
56551
57206
  );
56552
57207
  }
57208
+ const data = encodeFunctionData({
57209
+ abi: ERC20_TRANSFER_ABI,
57210
+ functionName: "transfer",
57211
+ args: [to, amountAtomic]
57212
+ });
57213
+ const networkFee = await evmGasPrecheck({
57214
+ client: pub,
57215
+ chainId,
57216
+ from: sender,
57217
+ to: tokenAddress,
57218
+ data,
57219
+ estimateL1Fee: OP_STACK_CHAIN_IDS.has(chainId) ? () => estimateL1Fee(pub, {
57220
+ account: sender,
57221
+ to: tokenAddress,
57222
+ data,
57223
+ chain,
57224
+ gasPriceOracleAddress: OP_STACK_GAS_PRICE_ORACLE
57225
+ }) : null
57226
+ });
56553
57227
  if (dryRun) {
56554
57228
  emit({
56555
57229
  success: true,
@@ -56565,6 +57239,7 @@ async function main5(argv) {
56565
57239
  toMasked: maskAddress2(to),
56566
57240
  fromMasked: maskAddress2(sender)
56567
57241
  },
57242
+ networkFee,
56568
57243
  confirmedAt: state.confirmation?.confirmedAt ?? null,
56569
57244
  keySource,
56570
57245
  // Key NAMES only — a value from a .env is never echoed.
@@ -56574,11 +57249,6 @@ async function main5(argv) {
56574
57249
  });
56575
57250
  }
56576
57251
  const wallet = createWalletClient({ account, chain, transport: http(rpcUrl) });
56577
- const data = encodeFunctionData({
56578
- abi: ERC20_TRANSFER_ABI,
56579
- functionName: "transfer",
56580
- args: [to, amountAtomic]
56581
- });
56582
57252
  const request2 = await wallet.prepareTransactionRequest({
56583
57253
  to: tokenAddress,
56584
57254
  data,
@@ -56637,7 +57307,7 @@ async function main5(argv) {
56637
57307
  code: landed === false ? "BROADCAST_FAILED" : "BROADCAST_AMBIGUOUS",
56638
57308
  message: redact(err?.shortMessage || err?.message || "broadcast failed")
56639
57309
  },
56640
- signedTxHash: expectedTxHash,
57310
+ signedTxHash: publicHash(expectedTxHash),
56641
57311
  nonceBefore,
56642
57312
  foundOnChain: landed,
56643
57313
  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."
@@ -56661,7 +57331,7 @@ async function main5(argv) {
56661
57331
  confirmed: false,
56662
57332
  rozoPaymentId,
56663
57333
  linkId: state.linkId,
56664
- txHash,
57334
+ txHash: publicHash(txHash),
56665
57335
  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."
56666
57336
  },
56667
57337
  EXIT_UNCONFIRMED
@@ -56682,7 +57352,7 @@ async function main5(argv) {
56682
57352
  confirmed: succeeded,
56683
57353
  rozoPaymentId,
56684
57354
  linkId: state.linkId,
56685
- txHash,
57355
+ txHash: publicHash(txHash),
56686
57356
  blockNumber: receipt.blockNumber?.toString?.() ?? null,
56687
57357
  ...succeeded ? {} : { error: { code: outcome.code, message: "The transfer reverted on chain." } },
56688
57358
  sent: {
@@ -56700,8 +57370,8 @@ async function main5(argv) {
56700
57370
  outcome.exitCode
56701
57371
  );
56702
57372
  }
56703
- async function run5(argv = process.argv.slice(2)) {
56704
- return main5(argv);
57373
+ async function run6(argv = process.argv.slice(2)) {
57374
+ return main6(argv);
56705
57375
  }
56706
57376
 
56707
57377
  // scripts/src/send-sol.mjs
@@ -56771,7 +57441,7 @@ function createTransferCheckedInstruction(source, mint, destination, owner, amou
56771
57441
  var MEMO_PROGRAM_ID = new import_web32.PublicKey("MemoSq4gqABAXKb96qnH8TysNcWxMyWCqXgDLGmfcHr");
56772
57442
  var MAINNET_GENESIS = "5eykt4UsFv8P8NJdTREpY1vzqKqZKvdpKuc147dw2N9d";
56773
57443
  var B58_ALPHABET = "123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz";
56774
- async function main6(argv) {
57444
+ async function main7(argv) {
56775
57445
  const args = parseArgs(argv);
56776
57446
  const rozoPaymentId = assertRozoPaymentId(args["rozo-payment-id"] || args._[0]);
56777
57447
  assertNoTrackedDotEnv();
@@ -56868,6 +57538,34 @@ async function main6(argv) {
56868
57538
  );
56869
57539
  }
56870
57540
  }
57541
+ const tx = new import_web32.Transaction();
57542
+ tx.add(
57543
+ createTransferCheckedInstruction(
57544
+ fromAta,
57545
+ mint,
57546
+ toAta,
57547
+ keypair.publicKey,
57548
+ amountAtomic,
57549
+ mintInfo.decimals
57550
+ )
57551
+ );
57552
+ if (source.receiverMemo) {
57553
+ tx.add(
57554
+ new import_web32.TransactionInstruction({
57555
+ keys: [],
57556
+ programId: MEMO_PROGRAM_ID,
57557
+ data: Buffer.from(String(source.receiverMemo), "utf8")
57558
+ })
57559
+ );
57560
+ }
57561
+ const { blockhash, lastValidBlockHeight } = await connection.getLatestBlockhash("confirmed");
57562
+ tx.recentBlockhash = blockhash;
57563
+ tx.feePayer = keypair.publicKey;
57564
+ const networkFee = await solFeePrecheck({
57565
+ connection,
57566
+ payer: keypair.publicKey,
57567
+ message: tx.compileMessage()
57568
+ });
56871
57569
  if (dryRun) {
56872
57570
  emit({
56873
57571
  success: true,
@@ -56884,6 +57582,7 @@ async function main6(argv) {
56884
57582
  toIsTokenAccount,
56885
57583
  withMemo: Boolean(source.receiverMemo)
56886
57584
  },
57585
+ networkFee,
56887
57586
  confirmedAt: state.confirmation?.confirmedAt ?? null,
56888
57587
  keySource,
56889
57588
  // Key NAMES only — a value from a .env is never echoed.
@@ -56892,29 +57591,6 @@ async function main6(argv) {
56892
57591
  note: "Nothing was signed or broadcast. Add --send (without --dry-run) to execute."
56893
57592
  });
56894
57593
  }
56895
- const tx = new import_web32.Transaction();
56896
- tx.add(
56897
- createTransferCheckedInstruction(
56898
- fromAta,
56899
- mint,
56900
- toAta,
56901
- keypair.publicKey,
56902
- amountAtomic,
56903
- mintInfo.decimals
56904
- )
56905
- );
56906
- if (source.receiverMemo) {
56907
- tx.add(
56908
- new import_web32.TransactionInstruction({
56909
- keys: [],
56910
- programId: MEMO_PROGRAM_ID,
56911
- data: Buffer.from(String(source.receiverMemo), "utf8")
56912
- })
56913
- );
56914
- }
56915
- const { blockhash, lastValidBlockHeight } = await connection.getLatestBlockhash("confirmed");
56916
- tx.recentBlockhash = blockhash;
56917
- tx.feePayer = keypair.publicKey;
56918
57594
  tx.sign(keypair);
56919
57595
  const signature = tx.signatures[0]?.signature ? base58Encode(tx.signatures[0].signature) : null;
56920
57596
  await finalPayabilityCheck({
@@ -56966,7 +57642,7 @@ async function main6(argv) {
56966
57642
  code: landed ? "BROADCAST_AMBIGUOUS" : "BROADCAST_FAILED",
56967
57643
  message: redact(err?.message || "broadcast failed")
56968
57644
  },
56969
- signature,
57645
+ signature: publicHash(signature),
56970
57646
  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."
56971
57647
  },
56972
57648
  EXIT_ERROR
@@ -57003,7 +57679,7 @@ async function main6(argv) {
57003
57679
  confirmed: false,
57004
57680
  rozoPaymentId,
57005
57681
  linkId: state.linkId,
57006
- txHash: sent,
57682
+ txHash: publicHash(sent),
57007
57683
  error: {
57008
57684
  code: broadcastOutcome({ executionError }).code,
57009
57685
  message: `The transaction landed but failed on chain: ${redact(JSON.stringify(executionError))}`
@@ -57022,7 +57698,7 @@ async function main6(argv) {
57022
57698
  confirmed: false,
57023
57699
  rozoPaymentId,
57024
57700
  linkId: state.linkId,
57025
- txHash: sent,
57701
+ txHash: publicHash(sent),
57026
57702
  guidance: "Broadcast but not confirmed within the wait window. Do NOT resend. Poll status.js; receipt truth is the backend confirmedAt/amountReceived."
57027
57703
  },
57028
57704
  EXIT_UNCONFIRMED
@@ -57036,7 +57712,7 @@ async function main6(argv) {
57036
57712
  confirmed: true,
57037
57713
  rozoPaymentId,
57038
57714
  linkId: state.linkId,
57039
- txHash: sent,
57715
+ txHash: publicHash(sent),
57040
57716
  sent: {
57041
57717
  chain: chainName(900),
57042
57718
  tokenSymbol: source.tokenSymbol,
@@ -57064,8 +57740,8 @@ function base58Encode(buf) {
57064
57740
  }
57065
57741
  return out2;
57066
57742
  }
57067
- async function run6(argv = process.argv.slice(2)) {
57068
- return main6(argv);
57743
+ async function run7(argv = process.argv.slice(2)) {
57744
+ return main7(argv);
57069
57745
  }
57070
57746
 
57071
57747
  // scripts/src/cli.mjs
@@ -57268,6 +57944,49 @@ async function cmdStatus(opts) {
57268
57944
  out();
57269
57945
  return exitCode;
57270
57946
  }
57947
+ var LAYER_LABEL = {
57948
+ confirmed: green("confirmed"),
57949
+ detected: yellow("detected"),
57950
+ pending: yellow("pending"),
57951
+ none: dim("none"),
57952
+ unknown: yellow("unknown")
57953
+ };
57954
+ async function cmdReceipt(opts) {
57955
+ const argv = targetToArgs(opts.target);
57956
+ if (opts.provider) argv.push("--provider", opts.provider);
57957
+ if (!opts.json) out(dim(" Reading the order\u2026 (no money moves)"));
57958
+ const { payload, exitCode } = await step(run5, argv);
57959
+ if (opts.json) {
57960
+ printJson(payload);
57961
+ return exitCode;
57962
+ }
57963
+ if (payload.error) {
57964
+ printError(payload);
57965
+ printSupport();
57966
+ return exitCode;
57967
+ }
57968
+ const r = payload.receipt;
57969
+ out();
57970
+ out(` Outcome ${outcomeLabel(r.paymentOutcome)} ${dim(`(${r.state})`)}`);
57971
+ out(` Your payment ${LAYER_LABEL[r.sourcePayment.status] ?? r.sourcePayment.status}` + (r.sourcePayment.txHash ? ` ${r.sourcePayment.txHash}` : ""));
57972
+ if (r.sourcePayment.localSend && r.sourcePayment.status !== "confirmed") {
57973
+ out(dim(` this machine sent it (${r.sourcePayment.localSend.status})` + (r.sourcePayment.localSend.txHash ? `: ${r.sourcePayment.localSend.txHash}` : "")));
57974
+ }
57975
+ out(` Merchant invoice ${LAYER_LABEL[r.merchantSettlement.status] ?? r.merchantSettlement.status}` + (r.merchantSettlement.coinbaseStatus ? ` ${dim(r.merchantSettlement.coinbaseStatus)}` : ""));
57976
+ out(` Service delivered ${LAYER_LABEL.unknown}`);
57977
+ if (r.merchantSettlement.status === "confirmed") out(` ${dim(r.serviceDelivery.note)}`);
57978
+ out();
57979
+ out(` Next: ${r.nextAction.type === "contact_support" ? red(r.nextAction.message) : r.nextAction.message}`);
57980
+ if (payload.support) printSupport();
57981
+ out();
57982
+ return exitCode;
57983
+ }
57984
+ function outcomeLabel(outcome) {
57985
+ if (outcome === "settled") return green(bold(outcome));
57986
+ if (outcome === "needs_attention" || outcome === "unknown") return red(bold(outcome));
57987
+ if (outcome === "expired_unfunded") return yellow(bold(outcome));
57988
+ return bold(outcome);
57989
+ }
57271
57990
  function presetFor(chainId, tokenSymbol) {
57272
57991
  const hit = PICKER_OPTIONS.find(
57273
57992
  (o) => o.chainId === String(chainId) && o.tokenSymbol === String(tokenSymbol).toUpperCase()
@@ -57452,7 +58171,7 @@ async function cmdPay(opts) {
57452
58171
  }
57453
58172
  if (opts.send) {
57454
58173
  const family = chainFamily(deposit.chainId);
57455
- const sender = family === "evm" ? run5 : family === "solana" ? run6 : null;
58174
+ const sender = family === "evm" ? run6 : family === "solana" ? run7 : null;
57456
58175
  if (!sender) {
57457
58176
  const payload = formatFailure({
57458
58177
  code: "SEND_UNSUPPORTED_CHAIN",
@@ -57564,7 +58283,7 @@ function printDeposit(deposit, opts) {
57564
58283
  out(dim(" Send it exactly once."));
57565
58284
  out();
57566
58285
  }
57567
- async function main7() {
58286
+ async function main8() {
57568
58287
  let opts;
57569
58288
  try {
57570
58289
  opts = parseCliArgs(process.argv.slice(2));
@@ -57593,6 +58312,8 @@ async function main7() {
57593
58312
  return cmdQuote(opts);
57594
58313
  case "status":
57595
58314
  return cmdStatus(opts);
58315
+ case "receipt":
58316
+ return cmdReceipt(opts);
57596
58317
  case "pay":
57597
58318
  try {
57598
58319
  return await cmdPay(opts);
@@ -57613,7 +58334,7 @@ async function main7() {
57613
58334
  return EXIT_USAGE;
57614
58335
  }
57615
58336
  }
57616
- main7().then((code) => process.exit(code ?? EXIT_OK)).catch((err) => {
58337
+ main8().then((code) => process.exit(code ?? EXIT_OK)).catch((err) => {
57617
58338
  const payload = formatFailure(err);
57618
58339
  if (process.argv.includes("--json")) printJson(payload);
57619
58340
  else {