@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.
- package/README.md +63 -1
- package/package.json +1 -1
- package/scripts/dist/cli.js +1267 -126
- package/scripts/dist/create-order.js +341 -186
- package/scripts/dist/quote.js +9 -0
- package/scripts/dist/receipt.js +1195 -0
- package/scripts/dist/resume.js +1486 -0
- package/scripts/dist/send-evm.js +502 -215
- package/scripts/dist/send-sol.js +324 -229
- package/scripts/dist/status.js +405 -49
package/scripts/dist/send-sol.js
CHANGED
|
@@ -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
|
|
33434
|
-
import
|
|
33435
|
-
import
|
|
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 =
|
|
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 =
|
|
33699
|
+
const here = path5.dirname(fileURLToPath(moduleUrl));
|
|
33494
33700
|
return [
|
|
33495
|
-
|
|
33496
|
-
|
|
33497
|
-
|
|
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 =
|
|
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,
|