lnlink-server 1.1.7 → 1.1.9
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/dist/app.js +312 -312
- package/dist/build-info.json +1 -1
- package/dist/config.default.js +1 -0
- package/dist/index.js +809 -37
- package/dist/index.js.map +4 -4
- package/dist/package.json +1 -1
- package/dist/prisma/migrations/20260402072918_add_exchange_orders/migration.sql +25 -0
- package/dist/prisma/migrations/20260403022951_auto_update/migration.sql +27 -0
- package/dist/prisma/schema.prisma +21 -0
- package/dist/public/js/init.js +125 -90
- package/dist/setting.mainnet.json +2 -1
- package/dist/setting.regtest.json +4 -4
- package/dist/setting.testnet.json +2 -1
- package/package.json +2 -2
- package/prisma/migrations/20260402072918_add_exchange_orders/migration.sql +25 -0
- package/prisma/migrations/20260403022951_auto_update/migration.sql +27 -0
- package/prisma/schema.prisma +21 -0
package/dist/index.js
CHANGED
|
@@ -22,6 +22,7 @@ var require_config_default = __commonJS({
|
|
|
22
22
|
LINK_DATA_PATH: "",
|
|
23
23
|
LINK_FLASH_SITE_BASE_URL: "https://devofflash.unift.xyz",
|
|
24
24
|
LINK_LNFI_NODE_SITE_URL: "https://devoflnnode.unift.xyz",
|
|
25
|
+
LINK_RGB_PRICE_SERVER_URL: "https://api-oracle.lnfi.network",
|
|
25
26
|
LINK_OWNER: "",
|
|
26
27
|
LINK_SETTING_FILE_PATH: ""
|
|
27
28
|
// LINK_SETTING_FILE_PATH: "",
|
|
@@ -880,7 +881,9 @@ var require_constants = __commonJS({
|
|
|
880
881
|
"link_status",
|
|
881
882
|
"list_unspents",
|
|
882
883
|
"decode_rgb_invoice",
|
|
883
|
-
"decode_ln_invoice"
|
|
884
|
+
"decode_ln_invoice",
|
|
885
|
+
"get_exchange_rate",
|
|
886
|
+
"list_exchange_orders"
|
|
884
887
|
];
|
|
885
888
|
var OWNER_PERMISSIONS = [
|
|
886
889
|
"genseed",
|
|
@@ -922,6 +925,7 @@ var require_constants = __commonJS({
|
|
|
922
925
|
"pay_rgb_invoice",
|
|
923
926
|
"disconnect_peer",
|
|
924
927
|
"create_invoice",
|
|
928
|
+
"create_hodl_invoice",
|
|
925
929
|
"pay_invoice",
|
|
926
930
|
"backup_node",
|
|
927
931
|
"restore_node",
|
|
@@ -1286,6 +1290,163 @@ var require_events = __commonJS({
|
|
|
1286
1290
|
}
|
|
1287
1291
|
});
|
|
1288
1292
|
|
|
1293
|
+
// business/service/prisma/repositories/exchangeOrderRepository.js
|
|
1294
|
+
var require_exchangeOrderRepository = __commonJS({
|
|
1295
|
+
"business/service/prisma/repositories/exchangeOrderRepository.js"(exports2, module2) {
|
|
1296
|
+
var PrismaService = require_prismaService();
|
|
1297
|
+
var ExchangeOrderRepository = class {
|
|
1298
|
+
static {
|
|
1299
|
+
__name(this, "ExchangeOrderRepository");
|
|
1300
|
+
}
|
|
1301
|
+
constructor() {
|
|
1302
|
+
this.prisma = PrismaService.getInstance().getClient();
|
|
1303
|
+
}
|
|
1304
|
+
async createOrder(data) {
|
|
1305
|
+
const now = Math.floor(Date.now() / 1e3);
|
|
1306
|
+
return this.prisma.exchangeOrder.create({
|
|
1307
|
+
data: {
|
|
1308
|
+
payment_hash: data.payment_hash,
|
|
1309
|
+
btc_invoice: data.btc_invoice,
|
|
1310
|
+
btc_amount: data.btc_amount,
|
|
1311
|
+
rgb_invoice: data.rgb_invoice || null,
|
|
1312
|
+
asset_id: data.asset_id,
|
|
1313
|
+
asset_amount: data.asset_amount,
|
|
1314
|
+
preimage: data.preimage || null,
|
|
1315
|
+
status: data.status,
|
|
1316
|
+
error_message: data.error_message || null,
|
|
1317
|
+
htlc_expiry_at: data.htlc_expiry_at,
|
|
1318
|
+
created_at: data.created_at || now,
|
|
1319
|
+
updated_at: data.updated_at || now
|
|
1320
|
+
}
|
|
1321
|
+
});
|
|
1322
|
+
}
|
|
1323
|
+
async getOrderById(id) {
|
|
1324
|
+
return this.prisma.exchangeOrder.findUnique({ where: { id } });
|
|
1325
|
+
}
|
|
1326
|
+
async getOrderByPaymentHash(payment_hash) {
|
|
1327
|
+
return this.prisma.exchangeOrder.findUnique({ where: { payment_hash } });
|
|
1328
|
+
}
|
|
1329
|
+
async getOrdersByStatus(status) {
|
|
1330
|
+
return this.prisma.exchangeOrder.findMany({
|
|
1331
|
+
where: { status },
|
|
1332
|
+
orderBy: { created_at: "asc" }
|
|
1333
|
+
});
|
|
1334
|
+
}
|
|
1335
|
+
async getOrders({
|
|
1336
|
+
status,
|
|
1337
|
+
page = 1,
|
|
1338
|
+
page_size = 20
|
|
1339
|
+
} = {}) {
|
|
1340
|
+
const where = status ? { status } : {};
|
|
1341
|
+
const [list, total] = await Promise.all([
|
|
1342
|
+
this.prisma.exchangeOrder.findMany({
|
|
1343
|
+
where,
|
|
1344
|
+
orderBy: { created_at: "desc" },
|
|
1345
|
+
skip: (page - 1) * page_size,
|
|
1346
|
+
take: page_size
|
|
1347
|
+
}),
|
|
1348
|
+
this.prisma.exchangeOrder.count({ where })
|
|
1349
|
+
]);
|
|
1350
|
+
return { list, total };
|
|
1351
|
+
}
|
|
1352
|
+
async updateOrder(id, data) {
|
|
1353
|
+
return this.prisma.exchangeOrder.update({
|
|
1354
|
+
where: { id },
|
|
1355
|
+
data: {
|
|
1356
|
+
...data,
|
|
1357
|
+
updated_at: Math.floor(Date.now() / 1e3)
|
|
1358
|
+
}
|
|
1359
|
+
});
|
|
1360
|
+
}
|
|
1361
|
+
/**
|
|
1362
|
+
* Optimistic lock: only update if current status matches expectedStatus.
|
|
1363
|
+
* Returns true if update succeeded, false if status already changed.
|
|
1364
|
+
*/
|
|
1365
|
+
async updateOrderWithExpectedStatus(id, expectedStatus, data) {
|
|
1366
|
+
const result = await this.prisma.exchangeOrder.updateMany({
|
|
1367
|
+
where: { id, status: expectedStatus },
|
|
1368
|
+
data: {
|
|
1369
|
+
...data,
|
|
1370
|
+
updated_at: Math.floor(Date.now() / 1e3)
|
|
1371
|
+
}
|
|
1372
|
+
});
|
|
1373
|
+
return result.count > 0;
|
|
1374
|
+
}
|
|
1375
|
+
async getOrdersByStatusIn(statuses) {
|
|
1376
|
+
return this.prisma.exchangeOrder.findMany({
|
|
1377
|
+
where: { status: { in: statuses } },
|
|
1378
|
+
orderBy: { created_at: "asc" }
|
|
1379
|
+
});
|
|
1380
|
+
}
|
|
1381
|
+
};
|
|
1382
|
+
module2.exports = ExchangeOrderRepository;
|
|
1383
|
+
}
|
|
1384
|
+
});
|
|
1385
|
+
|
|
1386
|
+
// business/service/prisma/db/exchangeOrders.js
|
|
1387
|
+
var require_exchangeOrders = __commonJS({
|
|
1388
|
+
"business/service/prisma/db/exchangeOrders.js"(exports2, module2) {
|
|
1389
|
+
var Logger = require_linkLogger();
|
|
1390
|
+
var ExchangeOrderRepository = require_exchangeOrderRepository();
|
|
1391
|
+
var repo = null;
|
|
1392
|
+
function getRepo() {
|
|
1393
|
+
if (!repo) {
|
|
1394
|
+
repo = new ExchangeOrderRepository();
|
|
1395
|
+
}
|
|
1396
|
+
return repo;
|
|
1397
|
+
}
|
|
1398
|
+
__name(getRepo, "getRepo");
|
|
1399
|
+
module2.exports = {
|
|
1400
|
+
createExchangeOrder: /* @__PURE__ */ __name(async (data) => {
|
|
1401
|
+
const logger2 = new Logger("exchangeOrderDB");
|
|
1402
|
+
try {
|
|
1403
|
+
return await getRepo().createOrder(data);
|
|
1404
|
+
} catch (error) {
|
|
1405
|
+
logger2.error("Error creating exchange order:", error.message);
|
|
1406
|
+
throw error;
|
|
1407
|
+
}
|
|
1408
|
+
}, "createExchangeOrder"),
|
|
1409
|
+
getExchangeOrderById: /* @__PURE__ */ __name(async (id) => {
|
|
1410
|
+
return getRepo().getOrderById(id);
|
|
1411
|
+
}, "getExchangeOrderById"),
|
|
1412
|
+
getExchangeOrderByPaymentHash: /* @__PURE__ */ __name(async (payment_hash) => {
|
|
1413
|
+
return getRepo().getOrderByPaymentHash(payment_hash);
|
|
1414
|
+
}, "getExchangeOrderByPaymentHash"),
|
|
1415
|
+
getExchangeOrdersByStatus: /* @__PURE__ */ __name(async (status) => {
|
|
1416
|
+
return getRepo().getOrdersByStatus(status);
|
|
1417
|
+
}, "getExchangeOrdersByStatus"),
|
|
1418
|
+
getExchangeOrdersByStatusIn: /* @__PURE__ */ __name(async (statuses) => {
|
|
1419
|
+
return getRepo().getOrdersByStatusIn(statuses);
|
|
1420
|
+
}, "getExchangeOrdersByStatusIn"),
|
|
1421
|
+
getExchangeOrders: /* @__PURE__ */ __name(async (filters) => {
|
|
1422
|
+
return getRepo().getOrders(filters);
|
|
1423
|
+
}, "getExchangeOrders"),
|
|
1424
|
+
updateExchangeOrder: /* @__PURE__ */ __name(async (id, data) => {
|
|
1425
|
+
const logger2 = new Logger("exchangeOrderDB");
|
|
1426
|
+
try {
|
|
1427
|
+
return await getRepo().updateOrder(id, data);
|
|
1428
|
+
} catch (error) {
|
|
1429
|
+
logger2.error("Error updating exchange order:", error.message);
|
|
1430
|
+
throw error;
|
|
1431
|
+
}
|
|
1432
|
+
}, "updateExchangeOrder"),
|
|
1433
|
+
/**
|
|
1434
|
+
* Optimistic lock update: only succeeds if current status matches expectedStatus.
|
|
1435
|
+
* Returns true if updated, false if status already changed (stale).
|
|
1436
|
+
*/
|
|
1437
|
+
updateExchangeOrderWithExpectedStatus: /* @__PURE__ */ __name(async (id, expectedStatus, data) => {
|
|
1438
|
+
const logger2 = new Logger("exchangeOrderDB");
|
|
1439
|
+
try {
|
|
1440
|
+
return await getRepo().updateOrderWithExpectedStatus(id, expectedStatus, data);
|
|
1441
|
+
} catch (error) {
|
|
1442
|
+
logger2.error("Error updating exchange order (optimistic):", error.message);
|
|
1443
|
+
throw error;
|
|
1444
|
+
}
|
|
1445
|
+
}, "updateExchangeOrderWithExpectedStatus")
|
|
1446
|
+
};
|
|
1447
|
+
}
|
|
1448
|
+
});
|
|
1449
|
+
|
|
1289
1450
|
// business/service/prisma/repositories/orderRepository.js
|
|
1290
1451
|
var require_orderRepository = __commonJS({
|
|
1291
1452
|
"business/service/prisma/repositories/orderRepository.js"(exports2, module2) {
|
|
@@ -2532,6 +2693,7 @@ var require_dbService = __commonJS({
|
|
|
2532
2693
|
"business/service/prisma/dbService.js"(exports2, module2) {
|
|
2533
2694
|
var config = require_config();
|
|
2534
2695
|
var events = require_events();
|
|
2696
|
+
var exchangeOrders = require_exchangeOrders();
|
|
2535
2697
|
var orders = require_orders();
|
|
2536
2698
|
var transactions = require_transactions();
|
|
2537
2699
|
var users = require_users();
|
|
@@ -2542,6 +2704,7 @@ var require_dbService = __commonJS({
|
|
|
2542
2704
|
...users,
|
|
2543
2705
|
...transactions,
|
|
2544
2706
|
...orders,
|
|
2707
|
+
...exchangeOrders,
|
|
2545
2708
|
...utils
|
|
2546
2709
|
};
|
|
2547
2710
|
}
|
|
@@ -2651,6 +2814,7 @@ var require_getConfig = __commonJS({
|
|
|
2651
2814
|
officialUniverseServer,
|
|
2652
2815
|
priceOracle,
|
|
2653
2816
|
rgbProxy,
|
|
2817
|
+
rgbPriceServer,
|
|
2654
2818
|
enableTor
|
|
2655
2819
|
} = settingFileData;
|
|
2656
2820
|
const litdRgbConfig = {
|
|
@@ -2667,6 +2831,7 @@ var require_getConfig = __commonJS({
|
|
|
2667
2831
|
LINK_RGB_BITCOIND_RPCPASS: bitcoindPass,
|
|
2668
2832
|
LINK_RGB_ELECTRS_HOST: bitcoindIndex,
|
|
2669
2833
|
LINK_RGB_PROXY_ENDPOINT: rgbProxy,
|
|
2834
|
+
LINK_RGB_PRICE_SERVER_URL: rgbPriceServer || void 0,
|
|
2670
2835
|
LINK_RGB_REMOTE_NODE_PUBKEY: officialRgbPeer,
|
|
2671
2836
|
LINK_RGB_REMOTE_NODE_HOST: officialRgbPeerHost,
|
|
2672
2837
|
LINK_BITCOIND_RPCHOST: bitcoindRpcHost && bitcoindRpcPort ? `${bitcoindRpcHost}:${bitcoindRpcPort}` : void 0,
|
|
@@ -2900,13 +3065,16 @@ var require_getConfig = __commonJS({
|
|
|
2900
3065
|
}
|
|
2901
3066
|
combinedConfig.LINK_TOR_SOCKS_PORT = combinedConfig.LINK_TOR_SOCKS_PORT || 9050;
|
|
2902
3067
|
combinedConfig.LINK_TOR_CONTROL_PORT = combinedConfig.LINK_TOR_CONTROL_PORT || 9051;
|
|
2903
|
-
|
|
2904
|
-
|
|
2905
|
-
|
|
2906
|
-
|
|
2907
|
-
|
|
2908
|
-
|
|
2909
|
-
|
|
3068
|
+
const isExternalNodes = combinedConfig.LINK_EXTERNAL_NODES === "true" || combinedConfig.LINK_EXTERNAL_NODES === true;
|
|
3069
|
+
if (!isExternalNodes) {
|
|
3070
|
+
try {
|
|
3071
|
+
combinedConfig = await assignAvailablePorts(combinedConfig);
|
|
3072
|
+
} catch (portError) {
|
|
3073
|
+
console.warn(
|
|
3074
|
+
"Port assignment failed, using default ports:",
|
|
3075
|
+
portError.message
|
|
3076
|
+
);
|
|
3077
|
+
}
|
|
2910
3078
|
}
|
|
2911
3079
|
combinedConfig.LINK_GRPC_HOST = `localhost:${combinedConfig.LINK_LND_RPC_PORT}`;
|
|
2912
3080
|
combinedConfig.LINK_ENABLE_TOR = combinedConfig.LINK_ENABLE_TOR === "true" || combinedConfig.LINK_ENABLE_TOR === true;
|
|
@@ -5384,6 +5552,10 @@ var require_config2 = __commonJS({
|
|
|
5384
5552
|
}
|
|
5385
5553
|
try {
|
|
5386
5554
|
const config = getConfig2();
|
|
5555
|
+
const isExternalNodes = config.LINK_EXTERNAL_NODES === "true" || config.LINK_EXTERNAL_NODES === true;
|
|
5556
|
+
if (isExternalNodes) {
|
|
5557
|
+
return null;
|
|
5558
|
+
}
|
|
5387
5559
|
const torBinary = path2.join(config.LINK_BINARY_PATH || "", getBinaryName("tor"));
|
|
5388
5560
|
const output = execSync(`"${torBinary}" --hash-password "${TOR_CONTROL_PASSWORD}"`, {
|
|
5389
5561
|
encoding: "utf-8",
|
|
@@ -6109,6 +6281,7 @@ var require_client4 = __commonJS({
|
|
|
6109
6281
|
}
|
|
6110
6282
|
rgbClient = new RgbApiClient({
|
|
6111
6283
|
baseUrl: `http://127.0.0.1:${config.LINK_RGB_LISTENING_PORT}`
|
|
6284
|
+
// baseUrl: "http://35.221.95.26:9744",
|
|
6112
6285
|
});
|
|
6113
6286
|
}
|
|
6114
6287
|
return rgbClient;
|
|
@@ -7181,7 +7354,7 @@ var require_package = __commonJS({
|
|
|
7181
7354
|
"package.json"(exports2, module2) {
|
|
7182
7355
|
module2.exports = {
|
|
7183
7356
|
name: "lnlink-server",
|
|
7184
|
-
version: "1.1.
|
|
7357
|
+
version: "1.1.9",
|
|
7185
7358
|
private: false,
|
|
7186
7359
|
main: "dist/index.js",
|
|
7187
7360
|
files: [
|
|
@@ -7193,7 +7366,7 @@ var require_package = __commonJS({
|
|
|
7193
7366
|
build: "node build.js && node build.js --mode development --external all --entry electron",
|
|
7194
7367
|
"start:bin": "node scripts/start-bin.js",
|
|
7195
7368
|
"start:docker:dev": "dotenv -e .env.dev -- docker compose -f ./docker-compose.dev.yml up --build",
|
|
7196
|
-
"start:dev": 'dotenv -e .env.dev -- sh -c "prisma generate && (prisma migrate dev --name auto_update || prisma db push) &&
|
|
7369
|
+
"start:dev": 'dotenv -e .env.dev -- sh -c "prisma generate && (prisma migrate dev --name auto_update || prisma db push) && node ./app.js"',
|
|
7197
7370
|
"start:regtest": "docker compose --env-file ./.env.regtest -f ./docker-compose-lnlink.yml up --build",
|
|
7198
7371
|
"start:testnet": "docker compose --env-file ./.env.testnet -f ./docker-compose-lnlink.yml up --build",
|
|
7199
7372
|
"start:mainnet": "docker compose --env-file ./.env.mainnet -f ./docker-compose-lnlink.yml up --build",
|
|
@@ -8587,7 +8760,11 @@ var require_api = __commonJS({
|
|
|
8587
8760
|
socket.setTimeout(TIMEOUT);
|
|
8588
8761
|
socket.once("connect", () => {
|
|
8589
8762
|
socket.destroy();
|
|
8590
|
-
resolve({
|
|
8763
|
+
resolve({
|
|
8764
|
+
host,
|
|
8765
|
+
port,
|
|
8766
|
+
reachable: true
|
|
8767
|
+
});
|
|
8591
8768
|
});
|
|
8592
8769
|
socket.once("timeout", () => {
|
|
8593
8770
|
socket.destroy();
|
|
@@ -8604,10 +8781,26 @@ var require_api = __commonJS({
|
|
|
8604
8781
|
__name(tryConnect2, "tryConnect");
|
|
8605
8782
|
const net = require("node:net");
|
|
8606
8783
|
const TOR_DIR_AUTHORITIES = [
|
|
8607
|
-
{
|
|
8608
|
-
|
|
8609
|
-
|
|
8610
|
-
|
|
8784
|
+
{
|
|
8785
|
+
host: "128.31.0.34",
|
|
8786
|
+
port: 9131,
|
|
8787
|
+
name: "moria1"
|
|
8788
|
+
},
|
|
8789
|
+
{
|
|
8790
|
+
host: "193.23.244.244",
|
|
8791
|
+
port: 443,
|
|
8792
|
+
name: "dannenberg"
|
|
8793
|
+
},
|
|
8794
|
+
{
|
|
8795
|
+
host: "199.58.81.140",
|
|
8796
|
+
port: 80,
|
|
8797
|
+
name: "Faravahar"
|
|
8798
|
+
},
|
|
8799
|
+
{
|
|
8800
|
+
host: "86.59.21.38",
|
|
8801
|
+
port: 443,
|
|
8802
|
+
name: "gabelmoo"
|
|
8803
|
+
}
|
|
8611
8804
|
];
|
|
8612
8805
|
const TIMEOUT = 5e3;
|
|
8613
8806
|
let reachable = false;
|
|
@@ -9478,10 +9671,20 @@ var require_tapdService = __commonJS({
|
|
|
9478
9671
|
const decodedInvoice = decode(payment_request);
|
|
9479
9672
|
const routingInfo = decodedInvoice.tags.find((tag) => tag.tagName === "routing_info");
|
|
9480
9673
|
if (routingInfo && routingInfo.data && routingInfo.data.length > 0) {
|
|
9481
|
-
const
|
|
9482
|
-
|
|
9483
|
-
|
|
9484
|
-
|
|
9674
|
+
const route_hints = routingInfo.data.map((hop) => {
|
|
9675
|
+
return {
|
|
9676
|
+
hop_hints: [
|
|
9677
|
+
{
|
|
9678
|
+
node_id: hop.pubkey,
|
|
9679
|
+
chan_id: BigInt(`0x${hop.short_channel_id}`).toString(),
|
|
9680
|
+
fee_base_msat: hop.fee_base_msat,
|
|
9681
|
+
fee_proportional_millionths: hop.fee_proportional_millionths,
|
|
9682
|
+
cltv_expiry_delta: hop.cltv_expiry_delta
|
|
9683
|
+
}
|
|
9684
|
+
]
|
|
9685
|
+
};
|
|
9686
|
+
});
|
|
9687
|
+
paymentRequest.route_hints = route_hints;
|
|
9485
9688
|
}
|
|
9486
9689
|
const sendParams = {
|
|
9487
9690
|
asset_amount,
|
|
@@ -9690,7 +9893,7 @@ var require_tapdService = __commonJS({
|
|
|
9690
9893
|
if (!retIsConnectPeer) {
|
|
9691
9894
|
throw new Error("Peer not connected");
|
|
9692
9895
|
}
|
|
9693
|
-
const
|
|
9896
|
+
const createInvoiceArgs = {
|
|
9694
9897
|
asset_amount,
|
|
9695
9898
|
asset_id: Buffer2.from(asset_id, "hex"),
|
|
9696
9899
|
peer_pubkey: Buffer2.from(remotePubkey, "hex"),
|
|
@@ -9699,7 +9902,8 @@ var require_tapdService = __commonJS({
|
|
|
9699
9902
|
expiry,
|
|
9700
9903
|
description_hash: description_hash ? Buffer2.from(description_hash, "hex") : void 0
|
|
9701
9904
|
}
|
|
9702
|
-
}
|
|
9905
|
+
};
|
|
9906
|
+
const invoice = await tapChannelService.addInvoice(createInvoiceArgs);
|
|
9703
9907
|
const payment_req = invoice?.invoice_result?.payment_request;
|
|
9704
9908
|
if (payment_req) {
|
|
9705
9909
|
await sleep(500);
|
|
@@ -9853,7 +10057,7 @@ var require_constants2 = __commonJS({
|
|
|
9853
10057
|
SOCIAL_ID_MISMATCH: "Social id don't match",
|
|
9854
10058
|
METHOD_NOT_SUPPORTED: "Method not supported"
|
|
9855
10059
|
};
|
|
9856
|
-
var PRIVILEGED_METHODS = ["make_invoice", "pay_invoice"];
|
|
10060
|
+
var PRIVILEGED_METHODS = ["make_invoice", "pay_invoice", "create_hodl_invoice"];
|
|
9857
10061
|
var FLASH_ACCOUNT_METHODS = [
|
|
9858
10062
|
"make_invoice",
|
|
9859
10063
|
"pay_invoice",
|
|
@@ -11354,8 +11558,9 @@ var require_nwcProxy = __commonJS({
|
|
|
11354
11558
|
}
|
|
11355
11559
|
} else {
|
|
11356
11560
|
const filterChannelList = channelList.filter((item) => {
|
|
11357
|
-
const
|
|
11358
|
-
|
|
11561
|
+
const asset_genesis = item?.custom_channel_data?.assets?.[0]?.asset_utxo?.asset_genesis || item?.custom_channel_data?.funding_assets?.[0]?.asset_genesis;
|
|
11562
|
+
const custom_channel_asset_id = asset_genesis?.asset_id;
|
|
11563
|
+
return item.commitment_type.includes("TAPROOT") && item.active === true && custom_channel_asset_id === assetId;
|
|
11359
11564
|
});
|
|
11360
11565
|
if (filterChannelList.length > 0) {
|
|
11361
11566
|
return filterChannelList.sort((a, b) => {
|
|
@@ -11488,9 +11693,15 @@ var require_nwcProxy = __commonJS({
|
|
|
11488
11693
|
}
|
|
11489
11694
|
const create_at = Math.floor(Date.now() / 1e3);
|
|
11490
11695
|
const expire_at = create_at + (expiry ?? 5 * 60);
|
|
11696
|
+
const chan_id = await getBestOutgoingChainId(
|
|
11697
|
+
asset_id
|
|
11698
|
+
).catch(() => {
|
|
11699
|
+
return false;
|
|
11700
|
+
});
|
|
11491
11701
|
const invoice = await addInvoice({
|
|
11492
11702
|
asset_amount: amount,
|
|
11493
11703
|
asset_id,
|
|
11704
|
+
chan_id,
|
|
11494
11705
|
description,
|
|
11495
11706
|
description_hash,
|
|
11496
11707
|
expiry,
|
|
@@ -11627,7 +11838,9 @@ var require_nwcProxy = __commonJS({
|
|
|
11627
11838
|
result: {
|
|
11628
11839
|
preimage: payment?.payment_result?.payment_preimage,
|
|
11629
11840
|
payment_hash: payment?.payment_result?.payment_hash,
|
|
11630
|
-
asset_id: lnlinkUser.asset_id
|
|
11841
|
+
asset_id: lnlinkUser.asset_id,
|
|
11842
|
+
status: payment?.payment_result?.status,
|
|
11843
|
+
failure_reason: payment?.payment_result?.failure_reason
|
|
11631
11844
|
}
|
|
11632
11845
|
});
|
|
11633
11846
|
}
|
|
@@ -11803,7 +12016,7 @@ var require_nwcProxy = __commonJS({
|
|
|
11803
12016
|
user_npub: lnlinkUser.npub,
|
|
11804
12017
|
node_type: NODE_TYPE.LITD,
|
|
11805
12018
|
transaction_kind: TRANSACTION_KIND.LIGHTNING,
|
|
11806
|
-
asset_id
|
|
12019
|
+
asset_id: lnlinkUser?.asset_id || asset_id
|
|
11807
12020
|
});
|
|
11808
12021
|
total = allTotal;
|
|
11809
12022
|
return allList.map((item) => ({
|
|
@@ -12222,6 +12435,7 @@ var require_lightning = __commonJS({
|
|
|
12222
12435
|
push_msat,
|
|
12223
12436
|
asset_amount,
|
|
12224
12437
|
asset_id,
|
|
12438
|
+
push_asset_amount = 0,
|
|
12225
12439
|
isPublic = true,
|
|
12226
12440
|
with_anchors = true,
|
|
12227
12441
|
fee_base_msat = 1e3,
|
|
@@ -12234,12 +12448,11 @@ var require_lightning = __commonJS({
|
|
|
12234
12448
|
host,
|
|
12235
12449
|
capacity_sat,
|
|
12236
12450
|
push_msat,
|
|
12237
|
-
asset_amount,
|
|
12238
|
-
asset_id,
|
|
12239
12451
|
public: isPublic,
|
|
12240
12452
|
with_anchors,
|
|
12241
12453
|
fee_base_msat: Number(fee_base_msat),
|
|
12242
|
-
fee_proportional_millionths
|
|
12454
|
+
fee_proportional_millionths,
|
|
12455
|
+
...asset_id ? { asset_id, asset_amount, push_asset_amount } : {}
|
|
12243
12456
|
};
|
|
12244
12457
|
const { is_connected, peer } = await isConnectPeer({
|
|
12245
12458
|
pubkey
|
|
@@ -12950,6 +13163,190 @@ var require_rgb = __commonJS({
|
|
|
12950
13163
|
}
|
|
12951
13164
|
});
|
|
12952
13165
|
|
|
13166
|
+
// business/service/rgb/exchange.js
|
|
13167
|
+
var require_exchange = __commonJS({
|
|
13168
|
+
"business/service/rgb/exchange.js"(exports2, module2) {
|
|
13169
|
+
var { checkObjectArgs } = require_common();
|
|
13170
|
+
var { getConfigValue } = require_getConfig();
|
|
13171
|
+
var {
|
|
13172
|
+
createExchangeOrder,
|
|
13173
|
+
getExchangeOrderByPaymentHash,
|
|
13174
|
+
getExchangeOrderById,
|
|
13175
|
+
getExchangeOrders,
|
|
13176
|
+
updateExchangeOrder
|
|
13177
|
+
} = require_dbService();
|
|
13178
|
+
var Logger = require_linkLogger();
|
|
13179
|
+
var getRgbClient = require_client4();
|
|
13180
|
+
var { decodeLnInvoice, listChannels } = require_lightning();
|
|
13181
|
+
var EXCHANGE_ORDER_STATUS = {
|
|
13182
|
+
CREATED: "created",
|
|
13183
|
+
RGB_HODL_ISSUED: "rgb_hodl_issued",
|
|
13184
|
+
RGB_PAID: "rgb_paid",
|
|
13185
|
+
BTC_SENDING: "btc_sending",
|
|
13186
|
+
BTC_SENT: "btc_sent",
|
|
13187
|
+
COMPLETED: "completed",
|
|
13188
|
+
FAILED: "failed"
|
|
13189
|
+
};
|
|
13190
|
+
var HODL_BUFFER_SEC = 7200;
|
|
13191
|
+
async function createHodlInvoice({
|
|
13192
|
+
btc_invoice,
|
|
13193
|
+
asset_id,
|
|
13194
|
+
asset_amount
|
|
13195
|
+
}) {
|
|
13196
|
+
const logger2 = new Logger("rgb-exchange");
|
|
13197
|
+
checkObjectArgs({
|
|
13198
|
+
btc_invoice,
|
|
13199
|
+
asset_id,
|
|
13200
|
+
asset_amount
|
|
13201
|
+
}, ["btc_invoice", "asset_id", "asset_amount"]);
|
|
13202
|
+
const parsedAssetAmount = Number(asset_amount);
|
|
13203
|
+
if (!Number.isFinite(parsedAssetAmount) || parsedAssetAmount <= 0 || !Number.isInteger(parsedAssetAmount)) {
|
|
13204
|
+
throw new Error(`asset_amount must be a positive integer, got: ${asset_amount}`);
|
|
13205
|
+
}
|
|
13206
|
+
const decoded = await decodeLnInvoice({ invoice: btc_invoice });
|
|
13207
|
+
const {
|
|
13208
|
+
payment_hash,
|
|
13209
|
+
amt_msat,
|
|
13210
|
+
timestamp,
|
|
13211
|
+
expiry_sec: btc_expiry_sec
|
|
13212
|
+
} = decoded;
|
|
13213
|
+
if (!payment_hash) {
|
|
13214
|
+
throw new Error("Failed to extract payment_hash from BTC invoice");
|
|
13215
|
+
}
|
|
13216
|
+
if (!amt_msat) {
|
|
13217
|
+
throw new Error("BTC invoice has no amount");
|
|
13218
|
+
}
|
|
13219
|
+
const now = Math.floor(Date.now() / 1e3);
|
|
13220
|
+
const btcExpireAt = Number(timestamp) + Number(btc_expiry_sec);
|
|
13221
|
+
const remaining = btcExpireAt - now;
|
|
13222
|
+
if (remaining <= HODL_BUFFER_SEC) {
|
|
13223
|
+
throw new Error(`BTC invoice expires too soon (remaining: ${remaining}s, required > ${HODL_BUFFER_SEC}s)`);
|
|
13224
|
+
}
|
|
13225
|
+
const hodl_expiry_sec = remaining - HODL_BUFFER_SEC;
|
|
13226
|
+
const { channels = [] } = await listChannels();
|
|
13227
|
+
const btcOutbound = channels.filter((ch) => !ch.asset_id && (ch.is_usable !== false && ch.ready !== false)).reduce((sum, ch) => {
|
|
13228
|
+
const balance = Number(ch.outbound_balance_msat || ch.local_balance_msat || 0);
|
|
13229
|
+
return sum + balance;
|
|
13230
|
+
}, 0);
|
|
13231
|
+
if (btcOutbound === 0) {
|
|
13232
|
+
logger2.warn(`BTC outbound balance is 0 \u2014 verify channel field names. Sample channel: ${JSON.stringify(channels[0] || {})}`);
|
|
13233
|
+
}
|
|
13234
|
+
if (btcOutbound < Number(amt_msat)) {
|
|
13235
|
+
throw new Error(`Insufficient BTC outbound liquidity (available: ${btcOutbound} msat, required: ${amt_msat} msat)`);
|
|
13236
|
+
}
|
|
13237
|
+
const existing = await getExchangeOrderByPaymentHash(payment_hash);
|
|
13238
|
+
if (existing) {
|
|
13239
|
+
throw new Error(`Exchange order already exists for payment_hash: ${payment_hash}`);
|
|
13240
|
+
}
|
|
13241
|
+
const RGB_MIN_AMT_MSAT = 3 * 1e3 * 1e3;
|
|
13242
|
+
const rgbClient = getRgbClient();
|
|
13243
|
+
const invoiceResult = await rgbClient.lightning.createHodlInvoice({
|
|
13244
|
+
payment_hash,
|
|
13245
|
+
amt_msat: RGB_MIN_AMT_MSAT,
|
|
13246
|
+
expiry_sec: hodl_expiry_sec,
|
|
13247
|
+
asset_id,
|
|
13248
|
+
asset_amount: parsedAssetAmount
|
|
13249
|
+
});
|
|
13250
|
+
const htlc_expiry_at = now + hodl_expiry_sec;
|
|
13251
|
+
const order = await createExchangeOrder({
|
|
13252
|
+
payment_hash,
|
|
13253
|
+
btc_invoice,
|
|
13254
|
+
btc_amount: String(amt_msat),
|
|
13255
|
+
rgb_invoice: invoiceResult.invoice,
|
|
13256
|
+
asset_id,
|
|
13257
|
+
asset_amount: String(asset_amount),
|
|
13258
|
+
status: EXCHANGE_ORDER_STATUS.RGB_HODL_ISSUED,
|
|
13259
|
+
htlc_expiry_at,
|
|
13260
|
+
created_at: now,
|
|
13261
|
+
updated_at: now
|
|
13262
|
+
});
|
|
13263
|
+
logger2.info(`Exchange order created: id=${order.id}, payment_hash=${payment_hash}`);
|
|
13264
|
+
return {
|
|
13265
|
+
invoice: invoiceResult.invoice,
|
|
13266
|
+
payment_hash,
|
|
13267
|
+
expiry_sec: hodl_expiry_sec,
|
|
13268
|
+
exchange_order_id: order.id,
|
|
13269
|
+
btc_amount: String(amt_msat)
|
|
13270
|
+
};
|
|
13271
|
+
}
|
|
13272
|
+
__name(createHodlInvoice, "createHodlInvoice");
|
|
13273
|
+
var BTC_ASSET_ID = "0000000000000000000000000000000000000000000000000000000000000000";
|
|
13274
|
+
async function getExchangeRate({ asset_id }) {
|
|
13275
|
+
const logger2 = new Logger("rgb-exchange");
|
|
13276
|
+
checkObjectArgs({ asset_id }, ["asset_id"]);
|
|
13277
|
+
const priceServerUrl = getConfigValue("LINK_RGB_PRICE_SERVER_URL");
|
|
13278
|
+
if (!priceServerUrl) {
|
|
13279
|
+
throw new Error("Price server URL not configured (LINK_RGB_PRICE_SERVER_URL)");
|
|
13280
|
+
}
|
|
13281
|
+
if (!priceServerUrl.startsWith("http://") && !priceServerUrl.startsWith("https://")) {
|
|
13282
|
+
throw new Error(`Invalid price server URL scheme: ${priceServerUrl}`);
|
|
13283
|
+
}
|
|
13284
|
+
const baseUrl = priceServerUrl.replace(/\/$/, "");
|
|
13285
|
+
const url = `${baseUrl}/price/getOne?transaction_type=0&subjectAssetId=${encodeURIComponent(asset_id)}&paymentAssetId=${BTC_ASSET_ID}`;
|
|
13286
|
+
const res = await fetch(url);
|
|
13287
|
+
if (!res.ok) {
|
|
13288
|
+
throw new Error(`Price oracle error: ${res.status} ${res.statusText}`);
|
|
13289
|
+
}
|
|
13290
|
+
const body = await res.json();
|
|
13291
|
+
if (!body.ok || !body.ok.assetRates) {
|
|
13292
|
+
throw new Error(`Price oracle returned error: ${JSON.stringify(body)}`);
|
|
13293
|
+
}
|
|
13294
|
+
const { assetRates } = body.ok;
|
|
13295
|
+
logger2.info(`Exchange rate fetched for asset ${asset_id}: coefficient=${assetRates.subjectAssetRate?.coefficient}, scale=${assetRates.subjectAssetRate?.scale}`);
|
|
13296
|
+
return { assetRates };
|
|
13297
|
+
}
|
|
13298
|
+
__name(getExchangeRate, "getExchangeRate");
|
|
13299
|
+
async function listExchangeOrders({
|
|
13300
|
+
status,
|
|
13301
|
+
page = 1,
|
|
13302
|
+
page_size = 20
|
|
13303
|
+
} = {}) {
|
|
13304
|
+
const validStatuses = Object.values(EXCHANGE_ORDER_STATUS);
|
|
13305
|
+
if (status && !validStatuses.includes(status)) {
|
|
13306
|
+
throw new Error(`Invalid status: ${status}. Valid values: ${validStatuses.join(", ")}`);
|
|
13307
|
+
}
|
|
13308
|
+
const safePage = Math.max(1, parseInt(page) || 1);
|
|
13309
|
+
const safePageSize = Math.min(100, Math.max(1, parseInt(page_size) || 20));
|
|
13310
|
+
const { list, total } = await getExchangeOrders({
|
|
13311
|
+
status,
|
|
13312
|
+
page: safePage,
|
|
13313
|
+
page_size: safePageSize
|
|
13314
|
+
});
|
|
13315
|
+
return {
|
|
13316
|
+
orders: list.map((order) => ({
|
|
13317
|
+
id: order.id,
|
|
13318
|
+
payment_hash: order.payment_hash,
|
|
13319
|
+
btc_amount: order.btc_amount,
|
|
13320
|
+
asset_id: order.asset_id,
|
|
13321
|
+
asset_amount: order.asset_amount,
|
|
13322
|
+
status: order.status,
|
|
13323
|
+
error_message: order.error_message,
|
|
13324
|
+
created_at: order.created_at,
|
|
13325
|
+
updated_at: order.updated_at
|
|
13326
|
+
})),
|
|
13327
|
+
total
|
|
13328
|
+
};
|
|
13329
|
+
}
|
|
13330
|
+
__name(listExchangeOrders, "listExchangeOrders");
|
|
13331
|
+
async function getExchangeOrder({ order_id }) {
|
|
13332
|
+
checkObjectArgs({ order_id }, ["order_id"]);
|
|
13333
|
+
const order = await getExchangeOrderById(Number(order_id));
|
|
13334
|
+
if (!order) {
|
|
13335
|
+
throw new Error(`Exchange order not found: ${order_id}`);
|
|
13336
|
+
}
|
|
13337
|
+
return order;
|
|
13338
|
+
}
|
|
13339
|
+
__name(getExchangeOrder, "getExchangeOrder");
|
|
13340
|
+
module2.exports = {
|
|
13341
|
+
EXCHANGE_ORDER_STATUS,
|
|
13342
|
+
createHodlInvoice,
|
|
13343
|
+
getExchangeRate,
|
|
13344
|
+
listExchangeOrders,
|
|
13345
|
+
getExchangeOrder
|
|
13346
|
+
};
|
|
13347
|
+
}
|
|
13348
|
+
});
|
|
13349
|
+
|
|
12953
13350
|
// business/service/proxy/rgbProxy.js
|
|
12954
13351
|
var require_rgbProxy = __commonJS({
|
|
12955
13352
|
"business/service/proxy/rgbProxy.js"(exports2, module2) {
|
|
@@ -12984,6 +13381,12 @@ var require_rgbProxy = __commonJS({
|
|
|
12984
13381
|
restoreNode,
|
|
12985
13382
|
getRGBAssetsList
|
|
12986
13383
|
} = require_rgb();
|
|
13384
|
+
var {
|
|
13385
|
+
createHodlInvoice,
|
|
13386
|
+
getExchangeRate,
|
|
13387
|
+
listExchangeOrders,
|
|
13388
|
+
getExchangeOrder
|
|
13389
|
+
} = require_exchange();
|
|
12987
13390
|
function rgbProxy() {
|
|
12988
13391
|
return {
|
|
12989
13392
|
// ---node manager
|
|
@@ -13173,6 +13576,35 @@ var require_rgbProxy = __commonJS({
|
|
|
13173
13576
|
readonly: true
|
|
13174
13577
|
};
|
|
13175
13578
|
}, "decode_ln_invoice"),
|
|
13579
|
+
// ---exchange
|
|
13580
|
+
create_hodl_invoice: /* @__PURE__ */ __name(async () => {
|
|
13581
|
+
return {
|
|
13582
|
+
method: createHodlInvoice,
|
|
13583
|
+
argNames: ["btc_invoice", "asset_id", "asset_amount"],
|
|
13584
|
+
readonly: false
|
|
13585
|
+
};
|
|
13586
|
+
}, "create_hodl_invoice"),
|
|
13587
|
+
get_exchange_rate: /* @__PURE__ */ __name(async () => {
|
|
13588
|
+
return {
|
|
13589
|
+
method: getExchangeRate,
|
|
13590
|
+
argNames: ["asset_id"],
|
|
13591
|
+
readonly: true
|
|
13592
|
+
};
|
|
13593
|
+
}, "get_exchange_rate"),
|
|
13594
|
+
list_exchange_orders: /* @__PURE__ */ __name(async () => {
|
|
13595
|
+
return {
|
|
13596
|
+
method: listExchangeOrders,
|
|
13597
|
+
argNames: ["status", "page", "page_size"],
|
|
13598
|
+
readonly: true
|
|
13599
|
+
};
|
|
13600
|
+
}, "list_exchange_orders"),
|
|
13601
|
+
get_exchange_order: /* @__PURE__ */ __name(async () => {
|
|
13602
|
+
return {
|
|
13603
|
+
method: getExchangeOrder,
|
|
13604
|
+
argNames: ["order_id"],
|
|
13605
|
+
readonly: true
|
|
13606
|
+
};
|
|
13607
|
+
}, "get_exchange_order"),
|
|
13176
13608
|
backup_node: /* @__PURE__ */ __name(async () => {
|
|
13177
13609
|
return {
|
|
13178
13610
|
method: backupNode,
|
|
@@ -14852,8 +15284,8 @@ var require_mempool = __commonJS({
|
|
|
14852
15284
|
const { LINK_NETWORK } = getConfig2();
|
|
14853
15285
|
if ((LINK_NETWORK || "").toLowerCase() === "regtest") {
|
|
14854
15286
|
return {
|
|
14855
|
-
apiBase: "http://34.84.
|
|
14856
|
-
pageBase: "http://34.84.
|
|
15287
|
+
apiBase: "http://34.84.69.164:8889/api",
|
|
15288
|
+
pageBase: "http://34.84.69.164:8889/zh"
|
|
14857
15289
|
};
|
|
14858
15290
|
}
|
|
14859
15291
|
return {
|
|
@@ -15748,6 +16180,321 @@ var require_pollBtcTransfers2 = __commonJS({
|
|
|
15748
16180
|
}
|
|
15749
16181
|
});
|
|
15750
16182
|
|
|
16183
|
+
// business/job/rgb/pollExchangeOrders.js
|
|
16184
|
+
var require_pollExchangeOrders = __commonJS({
|
|
16185
|
+
"business/job/rgb/pollExchangeOrders.js"(exports2, module2) {
|
|
16186
|
+
var {
|
|
16187
|
+
getExchangeOrdersByStatusIn,
|
|
16188
|
+
getExchangeOrderById,
|
|
16189
|
+
updateExchangeOrderWithExpectedStatus
|
|
16190
|
+
} = require_dbService();
|
|
16191
|
+
var getRgbClient = require_client4();
|
|
16192
|
+
var { EXCHANGE_ORDER_STATUS } = require_exchange();
|
|
16193
|
+
var { listPayments, listChannels } = require_lightning();
|
|
16194
|
+
var Logger = require_linkLogger();
|
|
16195
|
+
var POLL_STALE_MS = 6e4;
|
|
16196
|
+
var pollingStartedAt = 0;
|
|
16197
|
+
async function pollExchangeOrders() {
|
|
16198
|
+
const now = Date.now();
|
|
16199
|
+
if (pollingStartedAt > 0) {
|
|
16200
|
+
if (now - pollingStartedAt < POLL_STALE_MS) return;
|
|
16201
|
+
const logger2 = new Logger("poll-exchange-orders");
|
|
16202
|
+
logger2.warn(`Previous poll stuck for ${now - pollingStartedAt}ms, forcing re-entry`);
|
|
16203
|
+
}
|
|
16204
|
+
pollingStartedAt = now;
|
|
16205
|
+
try {
|
|
16206
|
+
const activeOrders = await getExchangeOrdersByStatusIn([
|
|
16207
|
+
EXCHANGE_ORDER_STATUS.RGB_HODL_ISSUED,
|
|
16208
|
+
EXCHANGE_ORDER_STATUS.RGB_PAID,
|
|
16209
|
+
EXCHANGE_ORDER_STATUS.BTC_SENDING,
|
|
16210
|
+
EXCHANGE_ORDER_STATUS.BTC_SENT
|
|
16211
|
+
]);
|
|
16212
|
+
if (!activeOrders || activeOrders.length === 0) {
|
|
16213
|
+
return;
|
|
16214
|
+
}
|
|
16215
|
+
const logger2 = new Logger("poll-exchange-orders");
|
|
16216
|
+
logger2.info(`Polling ${activeOrders.length} active exchange orders`);
|
|
16217
|
+
let payments = [];
|
|
16218
|
+
try {
|
|
16219
|
+
const paymentsResult = await listPayments();
|
|
16220
|
+
payments = paymentsResult && paymentsResult.payments ? paymentsResult.payments : [];
|
|
16221
|
+
} catch (error) {
|
|
16222
|
+
logger2.warn(`Failed to fetch listPayments, detection skipped: ${error.message}`);
|
|
16223
|
+
}
|
|
16224
|
+
const rgbClient = getRgbClient();
|
|
16225
|
+
for (const order of activeOrders) {
|
|
16226
|
+
try {
|
|
16227
|
+
await processOrder(order, payments, rgbClient, logger2);
|
|
16228
|
+
} catch (error) {
|
|
16229
|
+
logger2.error(`Error processing exchange order ${order.id}: ${error.message}`);
|
|
16230
|
+
}
|
|
16231
|
+
}
|
|
16232
|
+
} finally {
|
|
16233
|
+
pollingStartedAt = 0;
|
|
16234
|
+
}
|
|
16235
|
+
}
|
|
16236
|
+
__name(pollExchangeOrders, "pollExchangeOrders");
|
|
16237
|
+
var MAX_BTC_RETRIES = 10;
|
|
16238
|
+
async function processOrder(order, payments, rgbClient, logger2) {
|
|
16239
|
+
const now = Math.floor(Date.now() / 1e3);
|
|
16240
|
+
switch (order.status) {
|
|
16241
|
+
case EXCHANGE_ORDER_STATUS.RGB_HODL_ISSUED: {
|
|
16242
|
+
if (order.htlc_expiry_at && now > order.htlc_expiry_at) {
|
|
16243
|
+
const updated = await updateExchangeOrderWithExpectedStatus(
|
|
16244
|
+
order.id,
|
|
16245
|
+
EXCHANGE_ORDER_STATUS.RGB_HODL_ISSUED,
|
|
16246
|
+
{ status: EXCHANGE_ORDER_STATUS.FAILED, error_message: "HODL invoice expired, A never paid" }
|
|
16247
|
+
);
|
|
16248
|
+
if (updated) logger2.info(`Order ${order.id}: expired in rgb_hodl_issued, marked failed`);
|
|
16249
|
+
break;
|
|
16250
|
+
}
|
|
16251
|
+
const hodlPending = payments.find(
|
|
16252
|
+
(p) => p.payment_hash === order.payment_hash && p.inbound === true && p.status === "Pending" && p.asset_id
|
|
16253
|
+
);
|
|
16254
|
+
if (hodlPending) {
|
|
16255
|
+
const updated = await updateExchangeOrderWithExpectedStatus(
|
|
16256
|
+
order.id,
|
|
16257
|
+
EXCHANGE_ORDER_STATUS.RGB_HODL_ISSUED,
|
|
16258
|
+
{ status: EXCHANGE_ORDER_STATUS.RGB_PAID, error_message: null }
|
|
16259
|
+
);
|
|
16260
|
+
if (updated) {
|
|
16261
|
+
logger2.info(`Order ${order.id}: RGB HODL payment received (Pending), updated to rgb_paid`);
|
|
16262
|
+
const freshOrder = await getExchangeOrderById(order.id);
|
|
16263
|
+
if (freshOrder) await sendBtcPayment(freshOrder, payments, rgbClient, logger2);
|
|
16264
|
+
} else {
|
|
16265
|
+
logger2.warn(`Order ${order.id}: optimistic lock failed on rgb_hodl_issued -> rgb_paid`);
|
|
16266
|
+
}
|
|
16267
|
+
}
|
|
16268
|
+
break;
|
|
16269
|
+
}
|
|
16270
|
+
case EXCHANGE_ORDER_STATUS.RGB_PAID:
|
|
16271
|
+
case EXCHANGE_ORDER_STATUS.BTC_SENDING: {
|
|
16272
|
+
if (order.status === EXCHANGE_ORDER_STATUS.BTC_SENDING) {
|
|
16273
|
+
const alreadySent = payments.find(
|
|
16274
|
+
(p) => p.payment_hash === order.payment_hash && p.inbound === false && p.status === "Succeeded"
|
|
16275
|
+
);
|
|
16276
|
+
if (alreadySent) {
|
|
16277
|
+
const preimage = alreadySent.preimage || null;
|
|
16278
|
+
await updateExchangeOrderWithExpectedStatus(
|
|
16279
|
+
order.id,
|
|
16280
|
+
EXCHANGE_ORDER_STATUS.BTC_SENDING,
|
|
16281
|
+
{
|
|
16282
|
+
status: EXCHANGE_ORDER_STATUS.BTC_SENT,
|
|
16283
|
+
preimage,
|
|
16284
|
+
btc_retry_count: 0,
|
|
16285
|
+
error_message: null
|
|
16286
|
+
}
|
|
16287
|
+
);
|
|
16288
|
+
logger2.info(`Order ${order.id}: BTC payment already succeeded (crash recovery), advanced to btc_sent`);
|
|
16289
|
+
break;
|
|
16290
|
+
}
|
|
16291
|
+
}
|
|
16292
|
+
await sendBtcPayment(order, payments, rgbClient, logger2);
|
|
16293
|
+
break;
|
|
16294
|
+
}
|
|
16295
|
+
case EXCHANGE_ORDER_STATUS.BTC_SENT: {
|
|
16296
|
+
const btcSucceeded = payments.find(
|
|
16297
|
+
(p) => p.payment_hash === order.payment_hash && p.inbound === false && p.status === "Succeeded"
|
|
16298
|
+
);
|
|
16299
|
+
if (!btcSucceeded) {
|
|
16300
|
+
const btcFailed = payments.find(
|
|
16301
|
+
(p) => p.payment_hash === order.payment_hash && p.inbound === false && p.status === "Failed"
|
|
16302
|
+
);
|
|
16303
|
+
if (btcFailed) {
|
|
16304
|
+
const updated = await updateExchangeOrderWithExpectedStatus(
|
|
16305
|
+
order.id,
|
|
16306
|
+
EXCHANGE_ORDER_STATUS.BTC_SENT,
|
|
16307
|
+
{
|
|
16308
|
+
status: EXCHANGE_ORDER_STATUS.RGB_PAID,
|
|
16309
|
+
btc_retry_count: (order.btc_retry_count || 0) + 1,
|
|
16310
|
+
error_message: "BTC payment failed asynchronously, will retry"
|
|
16311
|
+
}
|
|
16312
|
+
);
|
|
16313
|
+
if (updated) logger2.warn(`Order ${order.id}: BTC payment failed async (retry ${(order.btc_retry_count || 0) + 1}/${MAX_BTC_RETRIES}), reverting to rgb_paid`);
|
|
16314
|
+
break;
|
|
16315
|
+
}
|
|
16316
|
+
}
|
|
16317
|
+
const hodlSucceeded = payments.find(
|
|
16318
|
+
(p) => p.payment_hash === order.payment_hash && p.inbound === true && p.status === "Succeeded" && p.asset_id
|
|
16319
|
+
);
|
|
16320
|
+
if (hodlSucceeded) {
|
|
16321
|
+
const updated = await updateExchangeOrderWithExpectedStatus(
|
|
16322
|
+
order.id,
|
|
16323
|
+
EXCHANGE_ORDER_STATUS.BTC_SENT,
|
|
16324
|
+
{
|
|
16325
|
+
status: EXCHANGE_ORDER_STATUS.COMPLETED,
|
|
16326
|
+
btc_retry_count: 0,
|
|
16327
|
+
error_message: null
|
|
16328
|
+
}
|
|
16329
|
+
);
|
|
16330
|
+
if (updated) logger2.info(`Order ${order.id}: RGB auto-claimed (Succeeded), marked completed`);
|
|
16331
|
+
} else {
|
|
16332
|
+
if (order.htlc_expiry_at && now > order.htlc_expiry_at) {
|
|
16333
|
+
const updated = await updateExchangeOrderWithExpectedStatus(
|
|
16334
|
+
order.id,
|
|
16335
|
+
EXCHANGE_ORDER_STATUS.BTC_SENT,
|
|
16336
|
+
{ status: EXCHANGE_ORDER_STATUS.FAILED, error_message: "HTLC expired: BTC sent but RGB auto-claim never fired (node event missed)" }
|
|
16337
|
+
);
|
|
16338
|
+
if (updated) logger2.error(`[LOSS] Order ${order.id}: HTLC expired in btc_sent, RGB not claimed. payment_hash=${order.payment_hash}`);
|
|
16339
|
+
} else if (order.htlc_expiry_at && now > order.htlc_expiry_at - 600) {
|
|
16340
|
+
logger2.error(`[ALERT] Order ${order.id} approaching HTLC expiry. payment_hash=${order.payment_hash}, preimage=${order.preimage}, htlc_expiry_at=${order.htlc_expiry_at}`);
|
|
16341
|
+
}
|
|
16342
|
+
}
|
|
16343
|
+
break;
|
|
16344
|
+
}
|
|
16345
|
+
}
|
|
16346
|
+
}
|
|
16347
|
+
__name(processOrder, "processOrder");
|
|
16348
|
+
async function sendBtcPayment(order, payments, rgbClient, logger2) {
|
|
16349
|
+
const now = Math.floor(Date.now() / 1e3);
|
|
16350
|
+
if (order.htlc_expiry_at && now > order.htlc_expiry_at - 300) {
|
|
16351
|
+
await updateExchangeOrderWithExpectedStatus(
|
|
16352
|
+
order.id,
|
|
16353
|
+
order.status,
|
|
16354
|
+
{ status: EXCHANGE_ORDER_STATUS.FAILED, error_message: "HTLC expiry too close, aborting BTC payment" }
|
|
16355
|
+
);
|
|
16356
|
+
logger2.error(`Order ${order.id}: HTLC expiry imminent (${order.htlc_expiry_at - now}s left), marking failed`);
|
|
16357
|
+
return;
|
|
16358
|
+
}
|
|
16359
|
+
const retries = order.btc_retry_count || 0;
|
|
16360
|
+
if (retries >= MAX_BTC_RETRIES) {
|
|
16361
|
+
await updateExchangeOrderWithExpectedStatus(
|
|
16362
|
+
order.id,
|
|
16363
|
+
order.status,
|
|
16364
|
+
{ status: EXCHANGE_ORDER_STATUS.FAILED, error_message: `BTC payment failed after ${MAX_BTC_RETRIES} retries` }
|
|
16365
|
+
);
|
|
16366
|
+
logger2.error(`Order ${order.id}: exceeded max BTC retries (${MAX_BTC_RETRIES})`);
|
|
16367
|
+
return;
|
|
16368
|
+
}
|
|
16369
|
+
if (!order.btc_invoice) {
|
|
16370
|
+
await updateExchangeOrderWithExpectedStatus(order.id, order.status, { status: EXCHANGE_ORDER_STATUS.FAILED, error_message: "Missing btc_invoice" });
|
|
16371
|
+
logger2.error(`Order ${order.id}: btc_invoice is null/empty, marking failed`);
|
|
16372
|
+
return;
|
|
16373
|
+
}
|
|
16374
|
+
try {
|
|
16375
|
+
const { channels = [] } = await listChannels();
|
|
16376
|
+
const btcOutbound = channels.filter((ch) => !ch.asset_id && (ch.is_usable !== false && ch.ready !== false)).reduce((sum, ch) => sum + Number(ch.outbound_balance_msat || ch.local_balance_msat || 0), 0);
|
|
16377
|
+
const requiredMsat = Number(order.btc_amount);
|
|
16378
|
+
if (!Number.isFinite(requiredMsat) || requiredMsat <= 0) {
|
|
16379
|
+
await updateExchangeOrderWithExpectedStatus(order.id, order.status, { status: EXCHANGE_ORDER_STATUS.FAILED, error_message: `Invalid btc_amount: ${order.btc_amount}` });
|
|
16380
|
+
logger2.error(`Order ${order.id}: invalid btc_amount "${order.btc_amount}", marking failed`);
|
|
16381
|
+
return;
|
|
16382
|
+
}
|
|
16383
|
+
if (btcOutbound < requiredMsat) {
|
|
16384
|
+
logger2.warn(`Order ${order.id}: insufficient BTC outbound (available: ${btcOutbound}, required: ${requiredMsat}), skipping this tick`);
|
|
16385
|
+
return;
|
|
16386
|
+
}
|
|
16387
|
+
} catch (error) {
|
|
16388
|
+
logger2.warn(`Order ${order.id}: failed to check BTC liquidity, proceeding anyway: ${error.message}`);
|
|
16389
|
+
}
|
|
16390
|
+
try {
|
|
16391
|
+
const lockAcquired = await updateExchangeOrderWithExpectedStatus(
|
|
16392
|
+
order.id,
|
|
16393
|
+
order.status,
|
|
16394
|
+
{ status: EXCHANGE_ORDER_STATUS.BTC_SENDING }
|
|
16395
|
+
);
|
|
16396
|
+
if (!lockAcquired) {
|
|
16397
|
+
logger2.warn(`Order ${order.id}: optimistic lock failed on ${order.status} -> btc_sending, skipping`);
|
|
16398
|
+
return;
|
|
16399
|
+
}
|
|
16400
|
+
const result = await rgbClient.lightning.payInvoice({ invoice: order.btc_invoice });
|
|
16401
|
+
const resultError = result && (result.error || result.failure_reason);
|
|
16402
|
+
if (resultError) {
|
|
16403
|
+
const errMsg = typeof resultError === "string" ? resultError : JSON.stringify(resultError);
|
|
16404
|
+
const isPermanent = /route.*not.*found|no.?route|expired/i.test(errMsg);
|
|
16405
|
+
if (isPermanent) {
|
|
16406
|
+
await updateExchangeOrderWithExpectedStatus(
|
|
16407
|
+
order.id,
|
|
16408
|
+
EXCHANGE_ORDER_STATUS.BTC_SENDING,
|
|
16409
|
+
{ status: EXCHANGE_ORDER_STATUS.FAILED, error_message: errMsg }
|
|
16410
|
+
);
|
|
16411
|
+
logger2.error(`Order ${order.id}: BTC payment permanently failed (from response): ${errMsg}`);
|
|
16412
|
+
} else {
|
|
16413
|
+
await updateExchangeOrderWithExpectedStatus(
|
|
16414
|
+
order.id,
|
|
16415
|
+
EXCHANGE_ORDER_STATUS.BTC_SENDING,
|
|
16416
|
+
{
|
|
16417
|
+
status: EXCHANGE_ORDER_STATUS.RGB_PAID,
|
|
16418
|
+
btc_retry_count: retries + 1,
|
|
16419
|
+
error_message: errMsg
|
|
16420
|
+
}
|
|
16421
|
+
);
|
|
16422
|
+
logger2.warn(`Order ${order.id}: BTC payment failed (from response, retry ${retries + 1}/${MAX_BTC_RETRIES}): ${errMsg}`);
|
|
16423
|
+
}
|
|
16424
|
+
return;
|
|
16425
|
+
}
|
|
16426
|
+
const preimage = result && result.preimage ? result.preimage : null;
|
|
16427
|
+
await updateExchangeOrderWithExpectedStatus(
|
|
16428
|
+
order.id,
|
|
16429
|
+
EXCHANGE_ORDER_STATUS.BTC_SENDING,
|
|
16430
|
+
{
|
|
16431
|
+
status: EXCHANGE_ORDER_STATUS.BTC_SENT,
|
|
16432
|
+
preimage,
|
|
16433
|
+
// Keep btc_retry_count — don't reset until completed.
|
|
16434
|
+
error_message: null
|
|
16435
|
+
}
|
|
16436
|
+
);
|
|
16437
|
+
if (!preimage) {
|
|
16438
|
+
logger2.warn(`Order ${order.id}: BTC payInvoice returned without preimage (retry ${retries}/${MAX_BTC_RETRIES}), payment may fail async`);
|
|
16439
|
+
} else {
|
|
16440
|
+
logger2.info(`Order ${order.id}: BTC payment sent, preimage=${preimage}`);
|
|
16441
|
+
}
|
|
16442
|
+
} catch (error) {
|
|
16443
|
+
const isTimeout = /timeout|ETIMEDOUT|ECONNRESET|socket hang up/i.test(error.message);
|
|
16444
|
+
if (isTimeout) {
|
|
16445
|
+
await updateExchangeOrderWithExpectedStatus(
|
|
16446
|
+
order.id,
|
|
16447
|
+
EXCHANGE_ORDER_STATUS.BTC_SENDING,
|
|
16448
|
+
{ error_message: `Ambiguous: ${error.message} \u2014 will re-check next tick` }
|
|
16449
|
+
);
|
|
16450
|
+
logger2.warn(`Order ${order.id}: BTC sendpayment ambiguous timeout, keeping btc_sending for re-check`);
|
|
16451
|
+
return;
|
|
16452
|
+
}
|
|
16453
|
+
const isAlreadyPaid = /already paid/i.test(error.message);
|
|
16454
|
+
if (isAlreadyPaid) {
|
|
16455
|
+
const matchedPayment = payments.find(
|
|
16456
|
+
(p) => p.payment_hash === order.payment_hash && p.inbound === false && p.status === "Succeeded"
|
|
16457
|
+
);
|
|
16458
|
+
await updateExchangeOrderWithExpectedStatus(
|
|
16459
|
+
order.id,
|
|
16460
|
+
EXCHANGE_ORDER_STATUS.BTC_SENDING,
|
|
16461
|
+
{
|
|
16462
|
+
status: EXCHANGE_ORDER_STATUS.BTC_SENT,
|
|
16463
|
+
preimage: matchedPayment?.preimage || null,
|
|
16464
|
+
btc_retry_count: 0,
|
|
16465
|
+
error_message: null
|
|
16466
|
+
}
|
|
16467
|
+
);
|
|
16468
|
+
logger2.warn(`Order ${order.id}: BTC "already paid" \u2014 treating as success, advanced to btc_sent`);
|
|
16469
|
+
return;
|
|
16470
|
+
}
|
|
16471
|
+
const isPermanent = /expired|no route/i.test(error.message);
|
|
16472
|
+
if (isPermanent) {
|
|
16473
|
+
await updateExchangeOrderWithExpectedStatus(
|
|
16474
|
+
order.id,
|
|
16475
|
+
EXCHANGE_ORDER_STATUS.BTC_SENDING,
|
|
16476
|
+
{ status: EXCHANGE_ORDER_STATUS.FAILED, error_message: error.message }
|
|
16477
|
+
);
|
|
16478
|
+
logger2.error(`Order ${order.id}: BTC sendpayment permanently failed: ${error.message}`);
|
|
16479
|
+
} else {
|
|
16480
|
+
await updateExchangeOrderWithExpectedStatus(
|
|
16481
|
+
order.id,
|
|
16482
|
+
EXCHANGE_ORDER_STATUS.BTC_SENDING,
|
|
16483
|
+
{
|
|
16484
|
+
status: EXCHANGE_ORDER_STATUS.RGB_PAID,
|
|
16485
|
+
btc_retry_count: retries + 1,
|
|
16486
|
+
error_message: error.message
|
|
16487
|
+
}
|
|
16488
|
+
);
|
|
16489
|
+
logger2.warn(`Order ${order.id}: BTC sendpayment transient error (retry ${retries + 1}/${MAX_BTC_RETRIES}), will retry: ${error.message}`);
|
|
16490
|
+
}
|
|
16491
|
+
}
|
|
16492
|
+
}
|
|
16493
|
+
__name(sendBtcPayment, "sendBtcPayment");
|
|
16494
|
+
module2.exports = pollExchangeOrders;
|
|
16495
|
+
}
|
|
16496
|
+
});
|
|
16497
|
+
|
|
15751
16498
|
// business/job/rgb/pollLightningTransfers.js
|
|
15752
16499
|
var require_pollLightningTransfers = __commonJS({
|
|
15753
16500
|
"business/job/rgb/pollLightningTransfers.js"(exports2, module2) {
|
|
@@ -16642,6 +17389,7 @@ var require_tasks = __commonJS({
|
|
|
16642
17389
|
var pollInvoiceTransfers = require_pollInvoiceTransfers();
|
|
16643
17390
|
var rgbConnectPeer = require_connectPeer2();
|
|
16644
17391
|
var pollBtcTransfers = require_pollBtcTransfers2();
|
|
17392
|
+
var pollExchangeOrders = require_pollExchangeOrders();
|
|
16645
17393
|
var pollLightningTransfers = require_pollLightningTransfers();
|
|
16646
17394
|
var pollRgbTransfers = require_pollRgbTransfers();
|
|
16647
17395
|
var refreshTransfers = require_refreshTransfers();
|
|
@@ -16770,6 +17518,17 @@ var require_tasks = __commonJS({
|
|
|
16770
17518
|
timeout: 3e4,
|
|
16771
17519
|
description: "Poll RGB Lightning transfers"
|
|
16772
17520
|
},
|
|
17521
|
+
rgb_poll_exchange_orders: {
|
|
17522
|
+
type: "cron",
|
|
17523
|
+
schedule: "*/5 * * * * *",
|
|
17524
|
+
implementation: pollExchangeOrders,
|
|
17525
|
+
gate: "rgbActive",
|
|
17526
|
+
priority: 1,
|
|
17527
|
+
enabled: true,
|
|
17528
|
+
maxRetries: 3,
|
|
17529
|
+
timeout: 3e4,
|
|
17530
|
+
description: "Poll RGB-BTC exchange orders and trigger BTC sendpayment on RGB receipt"
|
|
17531
|
+
},
|
|
16773
17532
|
// Event-driven tasks
|
|
16774
17533
|
bake_tapd_macaroon: {
|
|
16775
17534
|
type: "event",
|
|
@@ -19246,7 +20005,8 @@ var require_setting_mainnet = __commonJS({
|
|
|
19246
20005
|
officialRgbPeerHost: "CHANGE_ME_MAINNET_RGB_HOST:9736",
|
|
19247
20006
|
officialUniverseServer: "CHANGE_ME_MAINNET_UNIVERSE_HOST:10009",
|
|
19248
20007
|
priceOracle: "grpc-oracle.lnfi.network",
|
|
19249
|
-
rgbProxy: "rpc://CHANGE_ME_MAINNET_RGB_PROXY_HOST:5000/json-rpc"
|
|
20008
|
+
rgbProxy: "rpc://CHANGE_ME_MAINNET_RGB_PROXY_HOST:5000/json-rpc",
|
|
20009
|
+
rgbPriceServer: "https://api-oracle.lnfi.network"
|
|
19250
20010
|
};
|
|
19251
20011
|
}
|
|
19252
20012
|
});
|
|
@@ -19264,17 +20024,18 @@ var require_setting_regtest = __commonJS({
|
|
|
19264
20024
|
bitcoindZmqRawTx: "tcp://regtest.lnfi.network:28335",
|
|
19265
20025
|
network: "regtest",
|
|
19266
20026
|
nostrRelays: [
|
|
19267
|
-
"wss://
|
|
19268
|
-
"wss://
|
|
20027
|
+
"wss://relay01.lnfi.network",
|
|
20028
|
+
"wss://vault.iris.to"
|
|
19269
20029
|
],
|
|
19270
20030
|
officialLndPeer: "03b24a4bf911ffd26ac1d5e5f2440a3c2f6974e4cc85d2ef54e17ee6d3717433d3",
|
|
19271
|
-
officialLndPeerHost: "34.84.
|
|
20031
|
+
officialLndPeerHost: "34.84.69.164:7739",
|
|
19272
20032
|
officialNostrPubKey: "npub1me48869w43j30cfry9ayz9dsdl4gj54xppgk9krrv7g6hsq7psuqp3yusn",
|
|
19273
20033
|
officialRgbPeer: "03b7153e278882e48e690acd0743305cbada86b131ab3388ccd782b45b02f064ef",
|
|
19274
20034
|
officialRgbPeerHost: "regtest.lnfi.network:9736",
|
|
19275
20035
|
officialUniverseServer: "regtest.lnfi.network:10009",
|
|
19276
20036
|
priceOracle: "grpc-oracle.lnfi.network",
|
|
19277
|
-
rgbProxy: "rpc://regtest.lnfi.network:5000/json-rpc"
|
|
20037
|
+
rgbProxy: "rpc://regtest.lnfi.network:5000/json-rpc",
|
|
20038
|
+
rgbPriceServer: "https://api-oracle.lnfi.network"
|
|
19278
20039
|
};
|
|
19279
20040
|
}
|
|
19280
20041
|
});
|
|
@@ -19302,7 +20063,8 @@ var require_setting_testnet = __commonJS({
|
|
|
19302
20063
|
officialRgbPeerHost: "CHANGE_ME_TESTNET_RGB_HOST:9736",
|
|
19303
20064
|
officialUniverseServer: "CHANGE_ME_TESTNET_UNIVERSE_HOST:10009",
|
|
19304
20065
|
priceOracle: "grpc-oracle.lnfi.network",
|
|
19305
|
-
rgbProxy: "rpc://CHANGE_ME_TESTNET_RGB_PROXY_HOST:5000/json-rpc"
|
|
20066
|
+
rgbProxy: "rpc://CHANGE_ME_TESTNET_RGB_PROXY_HOST:5000/json-rpc",
|
|
20067
|
+
rgbPriceServer: "https://api-oracle.lnfi.network"
|
|
19306
20068
|
};
|
|
19307
20069
|
}
|
|
19308
20070
|
});
|
|
@@ -19336,6 +20098,16 @@ var require_initLinkConfig = __commonJS({
|
|
|
19336
20098
|
await updateMainLnlinkConfig({ node_name: LINK_NAME });
|
|
19337
20099
|
await reloadConfig();
|
|
19338
20100
|
}
|
|
20101
|
+
const networkTemplate = LINK_NETWORK === "testnet" ? testnetSettings : LINK_NETWORK === "mainnet" ? mainnetSettings : regtestSettings;
|
|
20102
|
+
const missingKeys = Object.keys(networkTemplate).filter(
|
|
20103
|
+
(k) => !(k in lndConfig.settings)
|
|
20104
|
+
);
|
|
20105
|
+
if (missingKeys.length > 0) {
|
|
20106
|
+
const patch = Object.fromEntries(missingKeys.map((k) => [k, networkTemplate[k]]));
|
|
20107
|
+
await updateMainLnlinkConfig({ settings: { ...lndConfig.settings, ...patch } });
|
|
20108
|
+
await reloadConfig();
|
|
20109
|
+
logger2.info(`Settings patched with new keys: ${missingKeys.join(", ")}`);
|
|
20110
|
+
}
|
|
19339
20111
|
return;
|
|
19340
20112
|
}
|
|
19341
20113
|
let settings;
|