diff --git a/package-lock.json b/package-lock.json index 1fcaaeb..1357c2f 100644 --- a/package-lock.json +++ b/package-lock.json @@ -6,7 +6,7 @@ "": { "name": "rgb-sdk-web-demo", "dependencies": { - "@utexo/rgb-sdk-web": "1.0.0-beta.10", + "@utexo/rgb-sdk-web": "1.0.0-beta.11", "react": "^18.3.0", "react-dom": "^18.3.0", "react-router-dom": "^6.26.0", @@ -1389,9 +1389,9 @@ } }, "node_modules/@swc/core": { - "version": "1.15.43", - "resolved": "https://registry.npmjs.org/@swc/core/-/core-1.15.43.tgz", - "integrity": "sha512-1CuKjFkPxIgGdeHVuNbkxmBxkcbdc08u0aiI43pFq6yY1tTVKmXT9hFEooyyKs/sJ3xf1GPHyEwTtk9Xl8dvQw==", + "version": "1.15.46", + "resolved": "https://registry.npmjs.org/@swc/core/-/core-1.15.46.tgz", + "integrity": "sha512-Ri3em2mBpq3h2zSPliCYl63otDGqek8PPEfv2nWgRQEbZ/VBCNyypVTVQ6cEbTCXBhy+WE2T3fQb08moIyuYaw==", "dev": true, "hasInstallScript": true, "license": "Apache-2.0", @@ -1407,18 +1407,18 @@ "url": "https://opencollective.com/swc" }, "optionalDependencies": { - "@swc/core-darwin-arm64": "1.15.43", - "@swc/core-darwin-x64": "1.15.43", - "@swc/core-linux-arm-gnueabihf": "1.15.43", - "@swc/core-linux-arm64-gnu": "1.15.43", - "@swc/core-linux-arm64-musl": "1.15.43", - "@swc/core-linux-ppc64-gnu": "1.15.43", - "@swc/core-linux-s390x-gnu": "1.15.43", - "@swc/core-linux-x64-gnu": "1.15.43", - "@swc/core-linux-x64-musl": "1.15.43", - "@swc/core-win32-arm64-msvc": "1.15.43", - "@swc/core-win32-ia32-msvc": "1.15.43", - "@swc/core-win32-x64-msvc": "1.15.43" + "@swc/core-darwin-arm64": "1.15.46", + "@swc/core-darwin-x64": "1.15.46", + "@swc/core-linux-arm-gnueabihf": "1.15.46", + "@swc/core-linux-arm64-gnu": "1.15.46", + "@swc/core-linux-arm64-musl": "1.15.46", + "@swc/core-linux-ppc64-gnu": "1.15.46", + "@swc/core-linux-s390x-gnu": "1.15.46", + "@swc/core-linux-x64-gnu": "1.15.46", + "@swc/core-linux-x64-musl": "1.15.46", + "@swc/core-win32-arm64-msvc": "1.15.46", + "@swc/core-win32-ia32-msvc": "1.15.46", + "@swc/core-win32-x64-msvc": "1.15.46" }, "peerDependencies": { "@swc/helpers": ">=0.5.17" @@ -1430,9 +1430,9 @@ } }, "node_modules/@swc/core-darwin-arm64": { - "version": "1.15.43", - "resolved": "https://registry.npmjs.org/@swc/core-darwin-arm64/-/core-darwin-arm64-1.15.43.tgz", - "integrity": "sha512-v1aVuvXdo/BHxJzco9V2xpHrvwWmhfS8t6gziY5wJxd+Z2h8AeJRnAwPD8itCDaGXVBwJ/CaKfxEzTkG0Va0OA==", + "version": "1.15.46", + "resolved": "https://registry.npmjs.org/@swc/core-darwin-arm64/-/core-darwin-arm64-1.15.46.tgz", + "integrity": "sha512-IsISIT22EfktVJrlvIpnAxG2u/A9aob9l99HMlx80x72WlFmFPk1V3UhkEzx86eJP8hw049KTFv/RISho2cq2Q==", "cpu": [ "arm64" ], @@ -1447,9 +1447,9 @@ } }, "node_modules/@swc/core-darwin-x64": { - "version": "1.15.43", - "resolved": "https://registry.npmjs.org/@swc/core-darwin-x64/-/core-darwin-x64-1.15.43.tgz", - "integrity": "sha512-lp3d4Lamc8dt5huYdGLSR+9hLxmfr1jb0l+4XXG2zPqZwYWRN9R0U2qYoTrggiU2RWW0oV9VbWM3kBnqIc2kdQ==", + "version": "1.15.46", + "resolved": "https://registry.npmjs.org/@swc/core-darwin-x64/-/core-darwin-x64-1.15.46.tgz", + "integrity": "sha512-4Tj4ppVIPCmUMpmGFiGtyEriwLyJ+yi/US4WfBrP/ok8COGddDZXLEzQETnKyK46mjvr1v0jevrS23zjoff7vA==", "cpu": [ "x64" ], @@ -1464,9 +1464,9 @@ } }, "node_modules/@swc/core-linux-arm-gnueabihf": { - "version": "1.15.43", - "resolved": "https://registry.npmjs.org/@swc/core-linux-arm-gnueabihf/-/core-linux-arm-gnueabihf-1.15.43.tgz", - "integrity": "sha512-JWTQQELtsG5GgphDrr/XqqmM2pDN3cZqbMS0Mrg+iTiXL3F74sn/S2IyYE/5u4h2KLkTf9qQ7dXyxsbx7YzkeA==", + "version": "1.15.46", + "resolved": "https://registry.npmjs.org/@swc/core-linux-arm-gnueabihf/-/core-linux-arm-gnueabihf-1.15.46.tgz", + "integrity": "sha512-i8tUGnNjyOgMmfmgFSg4aeJLQoFyfpIHK5FjpQAwpRyQIqEUB2w1e8zIDQzY1WhOxx8NoS1S5iUL813Un4Sf5A==", "cpu": [ "arm" ], @@ -1481,9 +1481,9 @@ } }, "node_modules/@swc/core-linux-arm64-gnu": { - "version": "1.15.43", - "resolved": "https://registry.npmjs.org/@swc/core-linux-arm64-gnu/-/core-linux-arm64-gnu-1.15.43.tgz", - "integrity": "sha512-B4otJRdPWIsmiSBf0uG7Z/+vMWmkufjz5MmYxubwKuZazDW14Zd3symga1N62QR4RT+kEFeHEgsXfZGyn/w0hw==", + "version": "1.15.46", + "resolved": "https://registry.npmjs.org/@swc/core-linux-arm64-gnu/-/core-linux-arm64-gnu-1.15.46.tgz", + "integrity": "sha512-c0OnhqzdhfOvv6qhNCcByepB+sNYOGZyhtr2Qa6ZCHvAWTYhSRw4j/u92Stue9PbZ/6q74b9nHzi76+kVzqQHQ==", "cpu": [ "arm64" ], @@ -1498,9 +1498,9 @@ } }, "node_modules/@swc/core-linux-arm64-musl": { - "version": "1.15.43", - "resolved": "https://registry.npmjs.org/@swc/core-linux-arm64-musl/-/core-linux-arm64-musl-1.15.43.tgz", - "integrity": "sha512-6zB6OnpViBxYy4tgY3v2i6AZY9fwkcHZ032UOwtwUuW1d19sdT07qF0kZe6/3UR1tUaK6jjg2rmVcUIBCEYVjQ==", + "version": "1.15.46", + "resolved": "https://registry.npmjs.org/@swc/core-linux-arm64-musl/-/core-linux-arm64-musl-1.15.46.tgz", + "integrity": "sha512-imyRpNEcUzFQFV2LE4jL68ErvmKEuZCbvZru77iQREunJ+bR4i658cupTgtG1mLYM3F1Tzy3Sb9xYb02KghWTg==", "cpu": [ "arm64" ], @@ -1515,9 +1515,9 @@ } }, "node_modules/@swc/core-linux-ppc64-gnu": { - "version": "1.15.43", - "resolved": "https://registry.npmjs.org/@swc/core-linux-ppc64-gnu/-/core-linux-ppc64-gnu-1.15.43.tgz", - "integrity": "sha512-coxE1ZWdB3uSDVNoEtYNrRi/1epvckZx9cTJ8ICUxTMTxGk+yvQ/Twacp3ruZSaMPGCriUjP86C37VhaT6nyRg==", + "version": "1.15.46", + "resolved": "https://registry.npmjs.org/@swc/core-linux-ppc64-gnu/-/core-linux-ppc64-gnu-1.15.46.tgz", + "integrity": "sha512-ctEfcl/HcUeomK33cbySiHZm98GEDIxTm1EkpBsYCiHxElYBzvTXVeuQT2YwbUXn9XCrjiw4ipyUNk33k26qRg==", "cpu": [ "ppc64" ], @@ -1532,9 +1532,9 @@ } }, "node_modules/@swc/core-linux-s390x-gnu": { - "version": "1.15.43", - "resolved": "https://registry.npmjs.org/@swc/core-linux-s390x-gnu/-/core-linux-s390x-gnu-1.15.43.tgz", - "integrity": "sha512-lXfLhs+LpBsD5inuYx+YDH5WsPPBQ95KPUiy8P5wq9ob9xKDZFqwNfU2QW6bGO8NqRO/H9JQomTSt5Yyh+FGfA==", + "version": "1.15.46", + "resolved": "https://registry.npmjs.org/@swc/core-linux-s390x-gnu/-/core-linux-s390x-gnu-1.15.46.tgz", + "integrity": "sha512-DxlMdnt84TtRVTv7WL/thWyz9+QU8QZNNoAP9rrk0P68LziuhfePp8MjQ44zIprpTHTsEwyziIuGUUN5iSC1bQ==", "cpu": [ "s390x" ], @@ -1549,9 +1549,9 @@ } }, "node_modules/@swc/core-linux-x64-gnu": { - "version": "1.15.43", - "resolved": "https://registry.npmjs.org/@swc/core-linux-x64-gnu/-/core-linux-x64-gnu-1.15.43.tgz", - "integrity": "sha512-07XnKwTmKy8TGOZG3D9fRnLWGynxPjwQnZLVmBFbo6F+7vHYzBIOuwXEhemrChBWb6yDNZsVCcMWCPX6FDD2xg==", + "version": "1.15.46", + "resolved": "https://registry.npmjs.org/@swc/core-linux-x64-gnu/-/core-linux-x64-gnu-1.15.46.tgz", + "integrity": "sha512-SKxI7J6t90XPl8hRUqtJi9NfGdunN/E/vZMc7Bc0figeRdOPDBT+Tm8g7cx9xM0T0mewh2l+8dewa3Am27/P+A==", "cpu": [ "x64" ], @@ -1566,9 +1566,9 @@ } }, "node_modules/@swc/core-linux-x64-musl": { - "version": "1.15.43", - "resolved": "https://registry.npmjs.org/@swc/core-linux-x64-musl/-/core-linux-x64-musl-1.15.43.tgz", - "integrity": "sha512-TJc+bsSIaBh+hZvZ5GRtW/K1bw66TJ9vsUwvVIsZdiWxU5ObLwZvfcnZ3UpgVfMnFibRes9uriJrQNBHEEogRQ==", + "version": "1.15.46", + "resolved": "https://registry.npmjs.org/@swc/core-linux-x64-musl/-/core-linux-x64-musl-1.15.46.tgz", + "integrity": "sha512-qj9T6B7bosI0VEsrWOVXZN1OXxS8Tp63ywyrLxNdOycnUtLdkgYcoBsN5y8ImnDDsnwrEWZOy1e+J4xSe7mA3Q==", "cpu": [ "x64" ], @@ -1583,9 +1583,9 @@ } }, "node_modules/@swc/core-win32-arm64-msvc": { - "version": "1.15.43", - "resolved": "https://registry.npmjs.org/@swc/core-win32-arm64-msvc/-/core-win32-arm64-msvc-1.15.43.tgz", - "integrity": "sha512-jfd7s2/bUQYkOHLs+LWQNKZdmDa8+sufKLllhpWAhVQ2GDCwsHe3vR/j+OSiItZNtkzFuaawa3+SAKz9y5gYfw==", + "version": "1.15.46", + "resolved": "https://registry.npmjs.org/@swc/core-win32-arm64-msvc/-/core-win32-arm64-msvc-1.15.46.tgz", + "integrity": "sha512-8p7l4c3LU+eA5g9Et1JPhNeMC1oQwXTGU+uah8DPIBX7YXzqswvaBtyKVmXefVGi/DJU1x3YJsc3mbAp9aWzSQ==", "cpu": [ "arm64" ], @@ -1600,9 +1600,9 @@ } }, "node_modules/@swc/core-win32-ia32-msvc": { - "version": "1.15.43", - "resolved": "https://registry.npmjs.org/@swc/core-win32-ia32-msvc/-/core-win32-ia32-msvc-1.15.43.tgz", - "integrity": "sha512-rLAE8JvucqEW1ZGohxPQrQWPBQeJG4+ypKbWfdlU/qmKScvCkxf9/Jxnzki1dkUQCQ7P5Enp13RlvqOlvx/32g==", + "version": "1.15.46", + "resolved": "https://registry.npmjs.org/@swc/core-win32-ia32-msvc/-/core-win32-ia32-msvc-1.15.46.tgz", + "integrity": "sha512-tUEnfr3Bn9u6FOjUb3PN9p+09qZC2j+wNDLKHzXXZn22rqGcUqR/ohCRSS+nG9B9+X+U+3FewNEHJkTmdIvMjQ==", "cpu": [ "ia32" ], @@ -1617,9 +1617,9 @@ } }, "node_modules/@swc/core-win32-x64-msvc": { - "version": "1.15.43", - "resolved": "https://registry.npmjs.org/@swc/core-win32-x64-msvc/-/core-win32-x64-msvc-1.15.43.tgz", - "integrity": "sha512-h8MLDHZcfIukwQWj03rIJZx1I0E81AYj2X7J/nGErG4nz+QAv6G1Z+peotvinL3lqpbo32tLYSMFo32/ySzxKg==", + "version": "1.15.46", + "resolved": "https://registry.npmjs.org/@swc/core-win32-x64-msvc/-/core-win32-x64-msvc-1.15.46.tgz", + "integrity": "sha512-Vux7UDzBJYQggSuPfcl2w9iu+IJpgpRCxHzgCaVkELnAXAE4XZMOTX9HNcaNiwfeIDqdu2rkr69RuDm6wY8neA==", "cpu": [ "x64" ], @@ -1651,9 +1651,9 @@ } }, "node_modules/@swc/wasm": { - "version": "1.15.43", - "resolved": "https://registry.npmjs.org/@swc/wasm/-/wasm-1.15.43.tgz", - "integrity": "sha512-jYqeckrzZGAU+9OSfmL15MWfhkKWRCC8QMssL/MZu/MaIP76mU3VHjzqxlwKIz9DOSR/9jCqi1+QlWE0ILNehA==", + "version": "1.15.46", + "resolved": "https://registry.npmjs.org/@swc/wasm/-/wasm-1.15.46.tgz", + "integrity": "sha512-XaHMuBVdMDZdi4h+V3471lo/R1aWefieDibevgiz7CFJPfAbmfqqQUPqpMYHx287+lrVWGeIbxS9swVEu8zruQ==", "dev": true, "license": "Apache-2.0" }, @@ -1753,9 +1753,9 @@ "integrity": "sha512-N0HL53PaVzbwGXch647iGkNN51r1T8mAm4hGnrBNf55gmt0ALuDf0LM0HvrNPgZbicHs4SH2gBRpJSqL3ePnog==" }, "node_modules/@utexo/rgb-sdk-core": { - "version": "1.0.0-beta.4", - "resolved": "https://registry.npmjs.org/@utexo/rgb-sdk-core/-/rgb-sdk-core-1.0.0-beta.4.tgz", - "integrity": "sha512-2CXOX8+6N2B/71smay/pJmlnYfYiGTyNFuemkeouSFGWMhCa9bcpq19y5kno608TC+A0JsO4vHFgQUXocn/Gyw==", + "version": "1.0.0-beta.5", + "resolved": "https://registry.npmjs.org/@utexo/rgb-sdk-core/-/rgb-sdk-core-1.0.0-beta.5.tgz", + "integrity": "sha512-YzgNuXg6knHcsGBjSCHbx/Eu4C1IZp8lYfj3iUlgy4c0n5Eyd9gDP+6cYwlfzDIqsDSdcIy9ELoDRgQdoG2/jA==", "license": "MIT", "dependencies": { "@noble/hashes": "^2.0.1", @@ -1765,24 +1765,24 @@ } }, "node_modules/@utexo/rgb-sdk-web": { - "version": "1.0.0-beta.10", - "resolved": "https://registry.npmjs.org/@utexo/rgb-sdk-web/-/rgb-sdk-web-1.0.0-beta.10.tgz", - "integrity": "sha512-JR+1KKcmcWAyOgLOzKymlLSr4DsOBzgpL09o9N/q4PTnhe9dU9qD0d43tKzWfXcydiyF94+zFJBvHkgv9NCefg==", + "version": "1.0.0-beta.11", + "resolved": "https://registry.npmjs.org/@utexo/rgb-sdk-web/-/rgb-sdk-web-1.0.0-beta.11.tgz", + "integrity": "sha512-OvDNzWIk6y9n3YTaKjyhvstahn4RhCYs0OLKceXL9pLAArdqrSh2GhxrjVHvb7ZU2oPj6RHPDSyXdpNZ8ICBGA==", "license": "MIT", "dependencies": { "@bitcoindevkit/bdk-wallet-web": "^0.2.0", "@noble/hashes": "^2.0.1", "@scure/btc-signer": "^2.0.1", "@utexo/rgb-lib-wasm": "^1.0.0-beta.3", - "@utexo/rgb-sdk-core": "^1.0.0-beta.4", - "@utexo/rln-wasm": "0.9.0-beta.3", + "@utexo/rgb-sdk-core": "^1.0.0-beta.5", + "@utexo/rln-wasm": "0.10.0-beta.3", "bitcoinjs-lib": "^6.1.7" } }, "node_modules/@utexo/rln-wasm": { - "version": "0.9.0-beta.3", - "resolved": "https://registry.npmjs.org/@utexo/rln-wasm/-/rln-wasm-0.9.0-beta.3.tgz", - "integrity": "sha512-IRfF2mJ26qg5hNWMY7L3StW2QF6GlckU8adST2HzUFKb+i7p0/tLvutwdm6hkJnX68VT8v0oAYTzxtsjHzrINw==" + "version": "0.10.0-beta.3", + "resolved": "https://registry.npmjs.org/@utexo/rln-wasm/-/rln-wasm-0.10.0-beta.3.tgz", + "integrity": "sha512-eMB5u+yYg1E4kcFpPOOMCjTHMyF86W0sqPXS7tzWnUpCgCWPYEBLkWeY2ZmE8uNKXeWJfsCdxBF9fwrpAJtkoA==" }, "node_modules/@vitejs/plugin-react": { "version": "4.7.0", @@ -1867,9 +1867,9 @@ } }, "node_modules/autoprefixer": { - "version": "10.5.3", - "resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-10.5.3.tgz", - "integrity": "sha512-bJRzflk8GgE4JX+iZNEwz9f9p460NCHnU7bd+CZ9vIjIlZuTkt6F3WSl2oNO8StZBFx17nLEsiQ6H2wcZiY7nA==", + "version": "10.5.4", + "resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-10.5.4.tgz", + "integrity": "sha512-MaU0U/za7N3r6brxD4YB/l4NSrFzLPlANv6wEuQVaIPlD3L4W9rFcQPbL/EilY9BHhHvhfcz3gInDLrEtWT4EA==", "dev": true, "funding": [ { @@ -1888,7 +1888,7 @@ "license": "MIT", "dependencies": { "browserslist": "^4.28.6", - "caniuse-lite": "^1.0.30001805", + "caniuse-lite": "^1.0.30001806", "fraction.js": "^5.3.4", "picocolors": "^1.1.1", "postcss-value-parser": "^4.2.0" @@ -1947,9 +1947,9 @@ "license": "MIT" }, "node_modules/baseline-browser-mapping": { - "version": "2.10.43", - "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.10.43.tgz", - "integrity": "sha512-AjYpR78kDWAY3Efj+cDTFH9t9SCoL7OoTp1BOb0mQV7S+6CiLwnWM3FyxhJtdPufDFKzmCSFoUncKjWgJEZTCQ==", + "version": "2.11.1", + "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.11.1.tgz", + "integrity": "sha512-HYXq73DDpCtNzOmrFsm9eSwCvWCql0RzqjpDzXN9EadiLJ4DNat0nsZ/Bzmy+Ud12mb4/zKDY0cQ805ZzN+i0A==", "dev": true, "license": "Apache-2.0", "bin": { @@ -2187,9 +2187,9 @@ } }, "node_modules/browserslist": { - "version": "4.28.6", - "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.28.6.tgz", - "integrity": "sha512-FQBYNK15VMslhLHpA7+n+n1GOlF1kId2xcCg7/j95f24AOF6VDYMNH4mFxF7KuaTdv627faazpOAjFzMrfJOUw==", + "version": "4.28.7", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.28.7.tgz", + "integrity": "sha512-JxV13hNrFxqjOc8alRbq9dK1MM79NEXYpma2B2J4wAtpWS5zIEIKqWPGCl7N4o7Uc7B7itylh7SuDujATRyyTw==", "dev": true, "funding": [ { @@ -2207,9 +2207,9 @@ ], "license": "MIT", "dependencies": { - "baseline-browser-mapping": "^2.10.42", - "caniuse-lite": "^1.0.30001803", - "electron-to-chromium": "^1.5.389", + "baseline-browser-mapping": "^2.10.44", + "caniuse-lite": "^1.0.30001806", + "electron-to-chromium": "^1.5.393", "node-releases": "^2.0.51", "update-browserslist-db": "^1.2.3" }, @@ -2351,9 +2351,9 @@ } }, "node_modules/caniuse-lite": { - "version": "1.0.30001805", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001805.tgz", - "integrity": "sha512-52noaS3DubycKSXaU30TwPGIp+POyQSUVa5jBEq3vkRkY0kjyb3LQgvhU6WGyCcyXqVLWO0Cw0Q6BSdD0kUfVA==", + "version": "1.0.30001806", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001806.tgz", + "integrity": "sha512-72Cuvd95zbSYPKq6Fhg8eDJRlzgWDf7/mtoZv6Qe/DYNCEBdNxoA3+rZAU2ZhGCpZlns3EssFavaZomckT5Uuw==", "dev": true, "funding": [ { @@ -2689,9 +2689,9 @@ } }, "node_modules/electron-to-chromium": { - "version": "1.5.392", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.392.tgz", - "integrity": "sha512-1yQq3VQCZRwsnYc67Oc+1fge6Lwtn0hzi6zmEVkB61Zx21kTbwJAW4dFLadl5Rc1tKhG/kSpYXnfiAhu0f0a1g==", + "version": "1.5.395", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.395.tgz", + "integrity": "sha512-7zt9Aw+SrmxLWLN0zhaTWZQiCdryLVrYTq5R7iZakLvi2UQPYMMsROYV/2qVCzMeCiSXHwKOU+sZ4zOVVlrtKA==", "dev": true, "license": "ISC" }, @@ -3902,9 +3902,9 @@ } }, "node_modules/postcss": { - "version": "8.5.19", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.19.tgz", - "integrity": "sha512-Mz8SaolMd8nB+G13WkORcxQKHZ/NE4xXevtkJHVuG+guo9/wYKlIMTKAqGdEmYOXR2ijPjTYNHssizdaVSUNdQ==", + "version": "8.5.22", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.22.tgz", + "integrity": "sha512-KBDEIpLrvpv16pp3K0Fw+UCoZfopFjjgeB+0tA/aaThfEE74kKDLrgg603YvOWJyg3+WYtyq3xYsQWsIyZlPqQ==", "dev": true, "funding": [ { @@ -3922,7 +3922,7 @@ ], "license": "MIT", "dependencies": { - "nanoid": "^3.3.12", + "nanoid": "^3.3.16", "picocolors": "^1.1.1", "source-map-js": "^1.2.1" }, diff --git a/package.json b/package.json index 83dd92e..c0ab8a7 100644 --- a/package.json +++ b/package.json @@ -8,7 +8,7 @@ "preview": "vite preview" }, "dependencies": { - "@utexo/rgb-sdk-web": "1.0.0-beta.10", + "@utexo/rgb-sdk-web": "1.0.0-beta.11", "react": "^18.3.0", "react-dom": "^18.3.0", "react-router-dom": "^6.26.0", diff --git a/src/components/RegtestLspFlow.tsx b/src/components/RegtestLspFlow.tsx index 3aa8a85..45092ce 100644 --- a/src/components/RegtestLspFlow.tsx +++ b/src/components/RegtestLspFlow.tsx @@ -368,7 +368,7 @@ export function RegtestLspFlow() { const payHash = String(res.txid ?? ''); log(`pay status: ${res.status ?? 'sent'} (payment hash ${payHash})`, 'ok'); - // Poll until Settled — each getLightningSendRequest poll also drives the wasm + // Poll until Settled — each getLightningSendStatus poll also drives the wasm // node's queued RGB work (HTLC/commitment coloring), without which the HTLC // never leaves this node. Mirrors the RN flow's sender-side settle loop. setPhase('settle'); @@ -377,12 +377,12 @@ export function RegtestLspFlow() { while (Date.now() < payDeadline) { await gatewayFund(address, 0.001, 1).catch(() => {}); await sleep(3000); - payStatus = await wallet.getLightningSendRequest(payHash); + payStatus = await wallet.getLightningSendStatus(payHash); log(` send status: ${payStatus ?? 'Pending'}`); - if (payStatus === 'Settled' || payStatus === 'Failed') break; + if (payStatus === 'Succeeded' || payStatus === 'Failed') break; } if (payStatus === 'Failed') throw new Error('payment Failed'); - if (payStatus !== 'Settled') log('send did not settle within timeout', 'err'); + if (payStatus !== 'Succeeded') log('send did not settle within timeout', 'err'); else log('payment Settled ✓', 'ok'); await showAssetBalance('sender (after)'); diff --git a/src/components/UtexoOps.tsx b/src/components/UtexoOps.tsx index 0affe4b..d620be1 100644 --- a/src/components/UtexoOps.tsx +++ b/src/components/UtexoOps.tsx @@ -51,8 +51,6 @@ export function UtexoOps({ utexo, walletId, network }: Props) { const [lnSendAssetId, setLnSendAssetId] = useState(''); const [lnSendAmount, setLnSendAmount] = useState(''); const [lnOut, setLnOut] = useState(''); - const [lnPendingPsbt, setLnPendingPsbt] = useState(null); - const [lnSignedPsbt, setLnSignedPsbt] = useState(null); // Validate balance const [validateAssetId, setValidateAssetId] = useState(''); @@ -246,11 +244,18 @@ export function UtexoOps({ utexo, walletId, network }: Props) { } catch (e) { setOnchainOut('Error: ' + e); addLog('onchainSend failed: ' + e, 'err'); } } + // `getOnchainSendStatus` was removed in v3 — an on-chain send's state is the + // state of its transfer, so read it from listOnchainTransfers by invoice. async function handleGetOnchainSendStatus() { if (!onchainInvoice.trim()) { setOnchainOut('Enter invoice'); return; } try { - const result = await utexo.getOnchainSendStatus(onchainInvoice.trim()); - setOnchainOut('Status: ' + json(result)); + const transfers = await utexo.listOnchainTransfers(onchainAssetId.trim() || undefined); + const match = transfers.filter((t) => t.invoiceString === onchainInvoice.trim()); + setOnchainOut( + match.length + ? 'Status: ' + json(match.map((t) => ({ status: t.status, kind: t.kind, txid: t.txid }))) + : 'No transfer found for that invoice' + ); } catch (e) { setOnchainOut('Error: ' + e); } } @@ -275,36 +280,6 @@ export function UtexoOps({ utexo, walletId, network }: Props) { } catch (e) { setLnOut('Error: ' + e); addLog('createLightningInvoice failed: ' + e, 'err'); } } - async function handlePayLnBegin() { - if (!lnInvoice.trim()) { setLnOut('Enter LN invoice'); return; } - try { - addLog('payLightningInvoiceBegin...', 'info'); - const psbt = await utexo.payLightningInvoiceBegin({ lnInvoice: lnInvoice.trim(), assetId: lnSendAssetId.trim() || undefined, amount: lnSendAmount ? parseInt(lnSendAmount) : undefined }); - setLnPendingPsbt(psbt); - setLnSignedPsbt(null); - setLnOut('Step 1 — Unsigned PSBT:\n' + psbt); - addLog('LN pay PSBT ready', 'ok'); - } catch (e) { setLnOut('Error: ' + e); addLog('payLightningInvoiceBegin failed: ' + e, 'err'); } - } - - async function handlePayLnSign() { - if (!lnPendingPsbt) { setLnOut('Run Step 1 first'); return; } - addLog('Signing LN PSBT...', 'info'); - const signed = await utexo.signPsbt(lnPendingPsbt); - setLnSignedPsbt(signed); - setLnOut('Step 2 — Signed PSBT:\n' + signed); - addLog('LN PSBT signed', 'ok'); - } - - async function handlePayLnEnd() { - if (!lnSignedPsbt) { setLnOut('Sign PSBT first'); return; } - addLog('payLightningInvoiceEnd...', 'info'); - const result = await utexo.payLightningInvoiceEnd({ signedPsbt: lnSignedPsbt }); - setLnPendingPsbt(null); setLnSignedPsbt(null); - setLnOut('Result:\n' + json(result)); - addLog('LN pay complete', 'ok'); - } - async function handlePayLnAuto() { if (!lnInvoice.trim()) { setLnOut('Enter LN invoice'); return; } addLog('payLightningInvoice (auto)...', 'info'); @@ -318,7 +293,7 @@ export function UtexoOps({ utexo, walletId, network }: Props) { async function handleGetLnSendRequest() { if (!lnInvoice.trim()) { setLnOut('Enter LN invoice'); return; } try { - const result = await utexo.getLightningSendRequest(lnInvoice.trim()); + const result = await utexo.getLightningSendStatus(lnInvoice.trim()); setLnOut('Status: ' + json(result)); } catch (e) { setLnOut('Error: ' + e); } } @@ -326,7 +301,7 @@ export function UtexoOps({ utexo, walletId, network }: Props) { async function handleGetLnReceiveRequest() { if (!lnInvoice.trim()) { setLnOut('Enter LN invoice'); return; } try { - const result = await utexo.getLightningReceiveRequest(lnInvoice.trim()); + const result = await utexo.getLightningReceiveStatus(lnInvoice.trim()); setLnOut('Status: ' + json(result)); } catch (e) { setLnOut('Error: ' + e); } } @@ -486,14 +461,10 @@ export function UtexoOps({ utexo, walletId, network }: Props) { Get LN Send Status Get LN Receive Status - + {/* v3: paying an LN invoice is atomic — the begin/sign/end trio is gone. */} +
+ Pay LN Invoice +
diff --git a/src/components/apay/RegularChannelFlow.tsx b/src/components/apay/RegularChannelFlow.tsx index a7ca5f3..e3a70b3 100644 --- a/src/components/apay/RegularChannelFlow.tsx +++ b/src/components/apay/RegularChannelFlow.tsx @@ -90,7 +90,26 @@ export function RegularChannelFlow() { {flow.closeRunning ? 'Closing…' : 'Close channel & settle on-chain'} + + {flow.fundingRunning + ? 'Funding…' + : `Wallet-funded open (${flow.fundWarm ? 'warm' : 'COLD'})`} + +

+ Wallet-funded open inverts the topology: the wasm node opens a channel to{' '} + regular_web (the daemon without --enable-virtual-channels-v0, the + only peer that accepts a browser-initiated open) and the app funds it itself via + buildLightningFundingTx → submitFundingTransaction. Known to stall at «pending awaiting + funding lock-in». When the funding tx never reaches the indexer, the run POSTs the identical + hex to esplora directly — if esplora accepts it the tx was always valid and our broadcast + path is the bug; if esplora rejects it, the tx itself is wrong. + {' '} + COLD vs warm is the experiment: clicked first in a fresh tab it bootstraps its own + wallet (create → fund → sync, no createUtxos, no payments) and opens immediately; clicked + after the main flow the BDK view is warm. If cold fails and warm passes, stale-view input + selection (§6.0l) is confirmed. +

Keysend tests (channel_issue.md), available after the flow completes — same channel, same amounts ({KEYSEND_REPRO_ASSET_AMOUNT} RGB), no invoice. wasm → hub settles normally; @@ -177,22 +196,68 @@ export function RegularChannelFlow() { style={ flow.closeOutcome.kind === 'settled' ? { borderColor: '#3fb950', color: '#3fb950', backgroundColor: '#3fb95010' } - : flow.closeOutcome.kind === 'partial' - ? { borderColor: '#d29922', color: '#d29922', backgroundColor: '#d2992210' } - : { borderColor: '#f85149', color: '#f85149', backgroundColor: '#f8514910' } + : { borderColor: '#f85149', color: '#f85149', backgroundColor: '#f8514910' } } >

{flow.closeOutcome.kind === 'settled' - ? '✓ Channel closed — split settled on-chain' + ? '✓ Channel closed — split settled on-chain (both sides)' : flow.closeOutcome.kind === 'partial' - ? '⚠ Closed — hub settled on-chain; wasm sweep not implemented' + ? '✗ Only the hub settled — the wasm post-close sweep did not land' : 'Close / on-chain settle incomplete'}
{flow.closeOutcome.detail} )} + {flow.funding && ( + + )} + {flow.fundingOutcome && ( +
+
+ {flow.fundingOutcome.kind === 'ready' + ? '✓ Wallet-funded channel ready' + : flow.fundingOutcome.kind === 'broadcast_broken' + ? '✗ SDK broadcast path is the bug (tx was valid)' + : flow.fundingOutcome.kind === 'tx_invalid' + ? '✗ Funding transaction itself is invalid' + : flow.fundingOutcome.kind === 'stalled' + ? '✗ Tx published, channel never locked in' + : 'Wallet-funded open inconclusive'} +
+ {flow.fundingOutcome.detail} +
+ )} + {flow.verdict && (
= { rc_pay: 'Pay →hub', rc_payback: 'Pay ←hub', rc_keysend: 'Keysend', + rc_funding: 'Wallet-funded open', done: 'Done', error: 'Error', }; @@ -167,6 +193,45 @@ export const short = (s: string, n = 24) => (s || '').slice(0, n) + ((s || '').length > n ? '…' : ''); export const normHash = (h: string) => (h || '').toLowerCase().replace(/^0x/, ''); +/** + * Has the indexer actually seen this transaction? + * + * Must be `GET /tx/`, which 404s on an unknown txid. Do NOT use + * `/tx//status` for presence: esplora answers that one with + * **HTTP 200 `{"confirmed":false}`** for a txid that has never existed, so an + * `r.ok` test there reports every transaction as broadcast — including one that + * was never published at all. + */ +export async function indexerTxSeen( + txid: string +): Promise<{ confirmed: boolean } | null> { + const r = await fetch(`${INDEXER_PROXY}/tx/${txid}`); + if (!r.ok) return null; // 404 — never seen, not even in the mempool + const tx = (await r.json().catch(() => null)) as { + status?: { confirmed?: boolean }; + } | null; + return { confirmed: !!tx?.status?.confirmed }; +} + +/** + * Push a raw tx straight to esplora, bypassing the SDK entirely. + * + * This is the bisect for §6.0s: if esplora accepts the same hex the SDK failed + * to broadcast, the transaction is valid and only our broadcast path is broken; + * if esplora rejects it, the transaction itself is wrong (stale-view input + * selection — the §6.0l race) and the broadcast path is innocent. + */ +export async function indexerBroadcast( + txHex: string +): Promise<{ accepted: boolean; body: string }> { + const r = await fetch(`${INDEXER_PROXY}/tx`, { + method: 'POST', + headers: { 'content-type': 'text/plain' }, + body: txHex, + }); + return { accepted: r.ok, body: (await r.text().catch(() => '')).trim() }; +} + export async function gatewayFund( address: string, amountBtc: number, diff --git a/src/components/apay/signet/useApaySignetFlow.ts b/src/components/apay/signet/useApaySignetFlow.ts index 6ff7a2c..74ddaf8 100644 --- a/src/components/apay/signet/useApaySignetFlow.ts +++ b/src/components/apay/signet/useApaySignetFlow.ts @@ -127,7 +127,7 @@ export function useApaySignetFlow(role: Role) { buyerSettledRef.current = false; addLog(`buyer payment hash received: ${short(m.paymentHash)} — watching settlement`); } - if (m.status === 'Settled') buyerSettledRef.current = true; + if (m.status === 'Succeeded') buyerSettledRef.current = true; setOtherStatus(`buyer payment: ${m.status}`); } else if (m.type === 'verdict' && role === 'buyer') { setCheckout({ ok: m.ok, soft: m.soft, detail: m.detail }); @@ -418,10 +418,10 @@ export function useApaySignetFlow(role: Role) { const pays = await wallet.listPayments().catch(() => []); const mp = pays.find((p) => normHash(p.paymentHash) === normHash(hash)); - const mpStatus = String(mp?.rawStatus ?? mp?.status ?? '').toLowerCase(); + const mpStatus = String(mp?.status ?? '').toLowerCase(); addLog( `watch: channel RGB ${nowRgb} (Δ${delta >= 0 ? '+' : ''}${delta}) inbound=${ - mp ? `${mp.inbound ? 'inbound' : 'outbound'}/${mp.rawStatus ?? mp.status}` : 'none' + mp ? `${mp.inbound ? 'inbound' : 'outbound'}/${mp.status}` : 'none' } buyerSettled=${buyerSettledRef.current}` ); @@ -594,14 +594,14 @@ export function useApaySignetFlow(role: Role) { while (Date.now() < settleDeadline) { checkAbort(); await sleep(POLL_MS); - payStatus = await wallet.getLightningSendRequest(pHash); + payStatus = await wallet.getLightningSendStatus(pHash); setSendStatus(payStatus ?? 'Pending'); post({ type: 'payment', paymentHash: pHash, status: payStatus ?? 'Pending' }); - addLog(` getLightningSendRequest: ${payStatus ?? 'Pending'}`); - if (payStatus === 'Settled' || payStatus === 'Failed') break; + addLog(` getLightningSendStatus: ${payStatus ?? 'Pending'}`); + if (payStatus === 'Succeeded' || payStatus === 'Failed') break; } if (payStatus === 'Failed') throw new Error('buyer payment Failed during LSP settlement'); - if (payStatus !== 'Settled') { + if (payStatus !== 'Succeeded') { throw new Error('Timeout — payment did not settle; ensure the Merchant window stays open.'); } diff --git a/src/components/apay/useApayFlow.ts b/src/components/apay/useApayFlow.ts index a84f9fd..9e2db91 100644 --- a/src/components/apay/useApayFlow.ts +++ b/src/components/apay/useApayFlow.ts @@ -157,7 +157,7 @@ export function useApayFlow(role: Role) { buyerSettledRef.current = false; addLog(`buyer payment hash received: ${short(m.paymentHash)} — watching settlement`); } - if (m.status === 'Settled') buyerSettledRef.current = true; + if (m.status === 'Succeeded') buyerSettledRef.current = true; setOtherStatus(`buyer payment: ${m.status}`); } else if (m.type === 'verdict' && role === 'buyer') { setCheckout({ ok: m.ok, soft: m.soft, detail: m.detail }); @@ -403,10 +403,10 @@ export function useApayFlow(role: Role) { const pays = await wallet.listPayments().catch(() => []); const mp = pays.find((p) => normHash(p.paymentHash) === normHash(hash)); - const mpStatus = String(mp?.rawStatus ?? mp?.status ?? '').toLowerCase(); + const mpStatus = String(mp?.status ?? '').toLowerCase(); addLog( `watch: channel RGB ${nowRgb} (Δ${delta >= 0 ? '+' : ''}${delta}) inbound=${ - mp ? `${mp.inbound ? 'inbound' : 'outbound'}/${mp.rawStatus ?? mp.status}` : 'none' + mp ? `${mp.inbound ? 'inbound' : 'outbound'}/${mp.status}` : 'none' } buyerSettled=${buyerSettledRef.current}` ); @@ -611,7 +611,7 @@ export function useApayFlow(role: Role) { post({ type: 'payment', paymentHash: pHash, status: sendResult.status ?? 'Pending' }); addLog('Cart paid — HTLC held at LSP, waiting for outbox settlement…', 'success'); - // Poll until Settled — each getLightningSendRequest poll also drives the + // Poll until Settled — each getLightningSendStatus poll also drives the // wasm node's queued RGB work (HTLC/commitment coloring). setPhase('settle'); const settleDeadline = Date.now() + SETTLE_TIMEOUT_S * 1000; @@ -620,14 +620,14 @@ export function useApayFlow(role: Role) { checkAbort(); await gatewayFund(address, 0.001, 1).catch(() => {}); await sleep(POLL_INTERVAL_MS); - payStatus = await wallet.getLightningSendRequest(pHash); + payStatus = await wallet.getLightningSendStatus(pHash); setSendStatus(payStatus ?? 'Pending'); post({ type: 'payment', paymentHash: pHash, status: payStatus ?? 'Pending' }); - addLog(` getLightningSendRequest: ${payStatus ?? 'Pending'}`); - if (payStatus === 'Settled' || payStatus === 'Failed') break; + addLog(` getLightningSendStatus: ${payStatus ?? 'Pending'}`); + if (payStatus === 'Succeeded' || payStatus === 'Failed') break; } if (payStatus === 'Failed') throw new Error('buyer payment Failed during LSP settlement'); - if (payStatus !== 'Settled') { + if (payStatus !== 'Succeeded') { throw new Error('Timeout — payment did not settle; ensure the Merchant window stays open.'); } diff --git a/src/components/apay/useRegularChannelFlow.ts b/src/components/apay/useRegularChannelFlow.ts index 4323617..14d5769 100644 --- a/src/components/apay/useRegularChannelFlow.ts +++ b/src/components/apay/useRegularChannelFlow.ts @@ -12,13 +12,18 @@ import { useCallback, useRef, useState } from 'react'; import { UTEXOWallet, generateKeys } from '@utexo/rgb-sdk-web'; import { useStore } from '../../store'; -import { DEMO_VSS_URL } from '../../lib/utils'; +import { DEMO_VSS_URL, mineBlocks } from '../../lib/utils'; import { CFG, CHANNEL_TIMEOUT_S, FAUCET_LDK_PORT, + FUNDING_BROADCAST_TIMEOUT_S, + FUNDING_CAPACITY_SAT, + FUNDING_READY_TIMEOUT_S, KEYSEND_REPRO_ASSET_AMOUNT, POLL_INTERVAL_MS, + REGULAR_LDK_PORT, + REGULAR_PUBKEY, REGULAR_CHANNEL_ASSET_AMOUNT, REGULAR_CHANNEL_CAPACITY_SAT, REGULAR_CHANNEL_PUSH_ASSET_AMOUNT, @@ -30,6 +35,8 @@ import { faucetGet, faucetPost, gatewayFund, + indexerBroadcast, + indexerTxSeen, short, sleep, type LogEntry, @@ -66,6 +73,32 @@ export interface CloseOutcome { detail: string; } +/** + * Outcome of the wallet-funded open repro (§6.0s). + * + * The three middle kinds are the whole point: when the channel does not become + * ready, they say *which* half is at fault instead of leaving it open. + * - `broadcast_broken` — esplora accepted the hex the SDK failed to publish + * - `tx_invalid` — esplora rejected it too; the tx itself is wrong + * - `stalled` — tx is in the mempool, channel still never locks in + */ +export interface FundingOutcome { + kind: 'ready' | 'broadcast_broken' | 'tx_invalid' | 'stalled' | 'inconclusive'; + detail: string; +} + +/** What the wallet-funded open produced, step by step. */ +export interface FundingInfo { + temporaryChannelId: string; + counterpartyNodeId: string; + channelValueSat: number; + txid: string; + /** Did the indexer see the tx from the SDK's own broadcast path? */ + sdkBroadcast: boolean | null; + /** Verbatim esplora reply to the independent POST /tx, when we had to probe. */ + probe?: string; +} + /** On-chain balances observed after the cooperative close. */ export interface OnchainSettle { wasmSettled: number; @@ -126,6 +159,9 @@ export function useRegularChannelFlow(mode: RegularFlowMode = 'full') { const [closeRunning, setCloseRunning] = useState(false); const [closeOutcome, setCloseOutcome] = useState(null); const [onchain, setOnchain] = useState(null); + const [funding, setFunding] = useState(null); + const [fundingOutcome, setFundingOutcome] = useState(null); + const [fundingRunning, setFundingRunning] = useState(false); const walletRef = useRef(null); const hubPubkeyRef = useRef(''); @@ -165,6 +201,123 @@ export function useRegularChannelFlow(mode: RegularFlowMode = 'full') { ); } + // Same shape as ensureHubConnected, and for the same reason: dialling a peer + // that is already connected does not no-op, it hangs until the handshake + // times out. A second funding run in one session hits exactly that. + async function ensureRegularConnected(wallet: UTEXOWallet, attempts = 4) { + let lastErr: unknown; + for (let i = 0; i < attempts; i++) { + checkAbort(); + try { + const peers = await wallet.listPeers().catch(() => []); + if (peers.some((p) => p.pubkey === REGULAR_PUBKEY && p.isConnected !== false)) { + addLog(' regular_web already connected — reusing the session'); + return; + } + await wallet.connectPeer(`${REGULAR_PUBKEY}@127.0.0.1:${REGULAR_LDK_PORT}`); + return; + } catch (e) { + lastErr = e; + addLog(` regular_web connect attempt ${i + 1}/${attempts}: ${e}`); + await sleep(2000); + } + } + throw new Error( + `could not connect to regular_web after ${attempts} attempts: ${lastErr}` + ); + } + + // Minimal wallet for the cold-start funding test: create, fund, sync, stop. + // Intentionally NOT what runFlow() does — no createUtxos, no channel, no + // payments — so the only difference from a warm run is how much the BDK + // wallet has done before input selection. + async function bootstrapColdWallet(): Promise { + const mnemonic = await stableMnemonic(); + const fresh = Date.now().toString(16); + addLog(`→ create wallet (cold) network=regtest dataDir=/funding_${fresh}`); + const wallet = new UTEXOWallet({ + network: 'regtest', + mnemonic, + password: 'apay-regular', + proxyUrl: CFG.gatewayWs, + transportEndpoint: CFG.transport, + indexerUrl: CFG.indexer, + skipConsistencyCheck: true, + dataDir: `/funding_${fresh}`, + nodeRuntimeId: `funding-${fresh}`, + vssUrl: DEMO_VSS_URL, + }); + await wallet.init(); + await wallet.unlock(); + walletRef.current = wallet; + tabHasFlowWallet = true; + if (!wallet.isOnline()) + throw new Error(`indexer unreachable at ${CFG.indexer} — is the LSP web stack running?`); + const address = await wallet.getAddress(); + addressRef.current = address; + addLog(`← wallet ready address=${short(address)}`, 'success'); + + addLog('→ gatewayFund btc=1 mine=6'); + await gatewayFund(address, 1, 6); + for (let i = 0; i < 20; i++) { + checkAbort(); + await wallet.syncWallet(); + const b = (await wallet.getBtcBalance()) as { vanilla?: { spendable?: number } }; + if ((b?.vanilla?.spendable ?? 0) > 0) break; + await sleep(2000); + } + addLog('← funded (cold — no createUtxos, no payments)', 'success'); + return wallet; + } + + // Wait for THIS funding tx's channel to lock in. Mining a block each pass is + // what confirms it; listChannels is the drive beat — the wasm node has no + // background executor, so a run that stops polling stops the channel dead + // even after the funding tx has plenty of confirmations. + // + // mineBlocks, not gatewayFund: this flow's whole subject is which UTXOs BDK + // picked, so it must not be paying the wallet while it waits. mineBlocks + // generates to bitcoind's own address and leaves the wallet untouched. + // + // Matching on fundingTxid, not peerPubkey: a second run in the same session + // opens a SECOND channel to the same peer, and a pubkey match would latch + // onto the first run's already-ready channel and report success in one poll + // — while the channel it actually opened never gets driven to ready. + async function waitFundedChannelReady(wallet: UTEXOWallet, fundingTxid: string) { + const deadline = Date.now() + FUNDING_READY_TIMEOUT_S * 1000; + while (Date.now() < deadline) { + checkAbort(); + await mineBlocks(1).catch(() => {}); + await sleep(POLL_INTERVAL_MS); + const channels = await wallet.listChannels().catch(() => []); + const chan = channels.find( + (c) => c.fundingTxid === fundingTxid || (!c.fundingTxid && c.peerPubkey === REGULAR_PUBKEY) + ); + // Full listChannels each pass, not just the matched row: with several + // channels to the same peer the interesting question is usually which of + // them moved, and a one-line summary hides exactly that. + addLog( + ` listChannels (${channels.length}): ` + + (channels + .map( + (c) => + `${short(c.fundingTxid ?? c.channelId, 10)}${ + c.fundingTxid === fundingTxid ? '*' : '' + }=${c.ready ? 'ready' : 'pending'}${c.isUsable ? '/usable' : ''}` + + `${c.assetId ? `/rgb${c.assetLocalAmount ?? 0}` : ''}` + ) + .join(' ') || '(none)') + ); + addLog( + ` channel ${short(fundingTxid, 12)}: ${ + chan ? (chan.ready ? 'ready' : 'pending') : 'absent' + }` + ); + if (chan?.ready) return chan; + } + return undefined; + } + // The relay ws session can drop while blocks confirm — reconnect quietly. async function ensureHubConnected(wallet: UTEXOWallet, attempts = 4) { let lastErr: unknown; @@ -174,7 +327,7 @@ export function useRegularChannelFlow(mode: RegularFlowMode = 'full') { const peers = await wallet.listPeers().catch(() => []); if (peers.some((p) => p.pubkey === hubPubkeyRef.current && p.isConnected !== false)) return; - await wallet.connectPeer(`127.0.0.1:${FAUCET_LDK_PORT}`, hubPubkeyRef.current); + await wallet.connectPeer(`${hubPubkeyRef.current}@127.0.0.1:${FAUCET_LDK_PORT}`); return; } catch (e) { lastErr = e; @@ -456,23 +609,23 @@ export function useRegularChannelFlow(mode: RegularFlowMode = 'full') { throw new Error('wasm → hub payment failed immediately'); } - // getLightningSendRequest polls getPayment — each call is also a drive beat. + // getLightningSendStatus polls getPayment — each call is also a drive beat. const sendDeadline = Date.now() + SETTLE_TIMEOUT_S * 1000; let sendStatus: string | null = 'WaitingCounterparty'; while (Date.now() < sendDeadline) { checkAbort(); await gatewayFund(address, 0.001, 1).catch(() => {}); await sleep(POLL_INTERVAL_MS); - sendStatus = await wallet.getLightningSendRequest(sent.txid); + sendStatus = await wallet.getLightningSendStatus(sent.txid); setPayment({ direction: 'wasm→hub', paymentHash: sent.txid, status: sendStatus ?? 'Pending', }); addLog(` wasm → hub: ${sendStatus ?? 'Pending'}`); - if (sendStatus === 'Settled' || sendStatus === 'Failed') break; + if (sendStatus === 'Succeeded' || sendStatus === 'Failed') break; } - if (sendStatus !== 'Settled') { + if (sendStatus !== 'Succeeded') { throw new Error(`wasm → hub payment did not settle (last status: ${sendStatus})`); } const wasmRgb1 = await waitWasmRgb( @@ -520,16 +673,16 @@ export function useRegularChannelFlow(mode: RegularFlowMode = 'full') { checkAbort(); await gatewayFund(address, 0.001, 1).catch(() => {}); await sleep(POLL_INTERVAL_MS); - recvStatus = await wallet.getLightningReceiveRequest(lnInvoice); + recvStatus = await wallet.getLightningReceiveStatus(lnInvoice); setPayment({ direction: 'hub→wasm', paymentHash: hubSent.payment_hash, status: recvStatus ?? 'Pending', }); addLog(` hub → wasm: ${recvStatus ?? 'Pending'}`); - if (recvStatus === 'Settled' || recvStatus === 'Failed') break; + if (recvStatus === 'Succeeded' || recvStatus === 'Failed') break; } - if (recvStatus !== 'Settled') { + if (recvStatus !== 'Succeeded') { throw new Error(`hub → wasm payment did not settle (last status: ${recvStatus})`); } const wasmRgb2 = await waitWasmRgb( @@ -698,7 +851,7 @@ export function useRegularChannelFlow(mode: RegularFlowMode = 'full') { await gatewayFund(addressRef.current, 0.001, 1).catch(() => {}); await sleep(POLL_INTERVAL_MS); const chan = await readWasmChannel(wallet); // drive beat - const status = await wallet.getLightningSendRequest(ks.paymentHash); + const status = await wallet.getLightningSendStatus(ks.paymentHash); setRepro({ direction: 'wasm→hub', paymentHash: ks.paymentHash, @@ -715,7 +868,7 @@ export function useRegularChannelFlow(mode: RegularFlowMode = 'full') { addLog('unexpected channel close during wasm → hub keysend', 'error'); return; } - if (status === 'Settled') { + if (status === 'Succeeded') { const nowRgb = Number(chan.assetLocalAmount ?? 0); const hubChan = await readHubChannel(wasmPubkeyRef.current).catch(() => undefined); setReproOutcome({ @@ -751,6 +904,181 @@ export function useRegularChannelFlow(mode: RegularFlowMode = 'full') { // eslint-disable-next-line react-hooks/exhaustive-deps }, [addLog]); + // ── Wallet-funded open (§6.0s repro) ──────────────────────────────────────── + // Every other flow here has the NATIVE side open the channel, so the native + // node builds and broadcasts the funding tx and the browser never funds + // anything. This one inverts that: the wasm node opens, and the app must fund + // it itself — + // + // openChannel → listPendingFundingRequests → buildLightningFundingTx + // → submitFundingTransaction → (broadcast) → channel_ready + // + // The peer is `regular_web`, NOT the hub: both hub daemons run with + // --enable-virtual-channels-v0 and reject a wasm-initiated open with + // `unsupported_scid_alias` (§6.0r). + // + // The known failure is that the channel stalls at "pending awaiting funding + // lock-in" because the funding tx never reaches the mempool. Rather than just + // reproducing that, this flow BISECTS it: when the indexer has not seen the + // tx, it POSTs the very same hex to esplora directly, bypassing the SDK. That + // single answer splits the two candidate causes apart — see FundingOutcome. + const runWalletFundedOpen = useCallback(async () => { + setFundingRunning(true); + setFunding(null); + setFundingOutcome(null); + const prevPhase = phase; + setPhase('rc_funding'); + try { + if (!REGULAR_PUBKEY) { + throw new Error( + 'VITE_REGULAR_PEER_PUBKEY is unset — re-run scripts/start-lsp-web.sh to ' + + 'write it into .env.local (the regular_web daemon must be up).' + ); + } + + // Cold start. Running this button without the main flow first is the + // actual experiment for §6.0s: the warm path reaches openChannel with a + // BDK view that has just been synced and pushed through four payments, + // and the stale-view input-selection theory (§6.0l) predicts only the + // warm path succeeds. So bootstrap the bare minimum — create, fund, sync — + // and deliberately skip createUtxos and every payment leg. + const wallet = walletRef.current ?? (await bootstrapColdWallet()); + const address = addressRef.current; + + // 1. Dial regular_web through the gateway relay (outbound-only, so the + // browser must connect first — same constraint as the hub). + addLog(`→ connectPeer regular_web ${short(REGULAR_PUBKEY)}@:${REGULAR_LDK_PORT}`); + await ensureRegularConnected(wallet); + + // 2. Open AND fund, in one call. The SDK owns the whole handshake now + // (openChannel → listPendingFundingRequests → buildLightningFundingTx + // → submitFundingTransaction), so this repro measures what an app + // actually calls rather than a sequence only this file performed. + // + // BTC-only: whether the funding tx reaches the mempool has nothing to + // do with the asset leg, and omitting it removes a variable. The fee + // rate comes from the wallet's feeRateSatVb, mirroring the native + // daemon's [rgb] fee_rate_sat_vb. + // + // No mining anywhere in here: FundingGenerationReady is a peer + // protocol event, and the wallet must not receive fresh UTXOs in the + // moments before BDK selects its inputs — that would perturb exactly + // what §6.0l suspects. Mine only where confirmations are genuinely + // awaited, i.e. waitFundedChannelReady. + addLog(`→ openChannel capacity=${FUNDING_CAPACITY_SAT} sat (no asset, funds itself)`); + const opened = await wallet.openChannel({ + peerPubkey: REGULAR_PUBKEY, + capacitySat: FUNDING_CAPACITY_SAT, + isPublic: false, + }); + const txid = opened.fundingTxid ?? ''; + const fundingTxHex = opened.fundingTxHex ?? ''; + if (!txid || !fundingTxHex) { + throw new Error('openChannel returned no funding tx — the channel is open but unfunded'); + } + addLog( + `← temporaryChannelId ${short(opened.temporaryChannelId, 32)} ` + + `funding txid=${short(txid)} ${fundingTxHex.length / 2} bytes` + ); + setFunding({ + temporaryChannelId: opened.temporaryChannelId, + counterpartyNodeId: REGULAR_PUBKEY, + channelValueSat: FUNDING_CAPACITY_SAT, + txid, + sdkBroadcast: null, + }); + + // 6. Did the SDK's broadcast path actually publish it? + let seen = false; + const bcDeadline = Date.now() + FUNDING_BROADCAST_TIMEOUT_S * 1000; + while (Date.now() < bcDeadline) { + checkAbort(); + await wallet.listChannels().catch(() => []); // drive beat + await mineBlocks(1).catch(() => {}); // mine-only — never pay the wallet mid-flow + const at = await indexerTxSeen(txid); + if (at) { + seen = true; + addLog(` indexer: funding tx present (${at.confirmed ? 'confirmed' : 'mempool'})`); + break; + } + addLog(' indexer: funding tx not seen yet'); + await sleep(POLL_INTERVAL_MS); + } + setFunding((f) => (f ? { ...f, sdkBroadcast: seen } : f)); + + // 7. THE BISECT. The SDK did not publish it — try esplora directly with + // the identical hex. Whatever esplora says settles which half is broken. + if (!seen) { + addLog(`indexer never saw ${short(txid)} — probing esplora directly`, 'error'); + const probe = await indexerBroadcast(fundingTxHex); + setFunding((f) => (f ? { ...f, probe: probe.body } : f)); + addLog(`← POST /tx ${probe.accepted ? 'ACCEPTED' : 'REJECTED'}: ${probe.body || '(empty)'}`); + + if (!probe.accepted) { + setFundingOutcome({ + kind: 'tx_invalid', + detail: + 'esplora rejected the same hex, so the SDK broadcast path is innocent — the ' + + `funding transaction itself is invalid. Most likely stale-view input selection ` + + `(§6.0l), which also explains why the one passing run was on a freshly ` + + `provisioned chain. esplora said: ${probe.body || '(empty response)'}`, + }); + addLog('funding tx is invalid — not a broadcast problem', 'error'); + return; + } + + // Accepted out-of-band: the tx was always fine. Keep going, because + // whether the channel now locks in tells us if out-of-band broadcast is + // a complete fix or only half of one. + addLog('esplora accepted it — the tx was valid all along; SDK broadcast is the bug', 'error'); + const ready = await waitFundedChannelReady(wallet, txid); + setFundingOutcome({ + kind: 'broadcast_broken', + detail: + 'The SDK never published the funding tx, but esplora accepted the identical hex ' + + 'on a direct POST — so the transaction was always valid and the broken half is ' + + 'our broadcast path (chainSyncEnqueueRebroadcastTx never flushes). ' + + (ready + ? 'After the out-of-band broadcast the channel became ready, so broadcasting ' + + 'out-of-band is a complete fix — which is what upstream’s flow.js does.' + : 'The channel still did NOT become ready afterwards, so broadcast is not the ' + + 'only problem — something downstream of lock-in is also wrong.'), + }); + return; + } + + // 8. The tx was published by the SDK. Does the channel lock in? + addLog('indexer saw the funding tx ✓ — waiting for channel_ready'); + const ready = await waitFundedChannelReady(wallet, txid); + if (ready) { + setFundingOutcome({ + kind: 'ready', + detail: + `Wallet-funded channel is ready ✓ — capacity ${ready.capacitySat} sat against ` + + 'regular_web. The SDK broadcast the funding tx itself; scenario I passed this run.', + }); + addLog('wallet-funded channel ready ✓', 'success'); + } else { + setFundingOutcome({ + kind: 'stalled', + detail: + 'The funding tx IS in the indexer, yet the channel never became ready — it is ' + + 'stuck at "pending awaiting funding lock-in". Broadcast is not the problem this ' + + 'run; check the peer log on regular_web for what it did with FundingSigned.', + }); + addLog('funding tx published but channel never locked in', 'error'); + } + } catch (e) { + const msg = (e as Error)?.message ?? String(e); + addLog(`wallet-funded open: ${msg}`, 'error'); + setFundingOutcome({ kind: 'inconclusive', detail: msg }); + } finally { + setFundingRunning(false); + setPhase(prevPhase === 'idle' ? 'done' : prevPhase); + } + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [addLog, phase]); + // ── Cooperative close + on-chain settle check: the hub initiates the close // (native /closechannel), the wasm side co-signs through the relay session. // The channel's final RGB split (asymmetric after the 10-out / 5-back legs) @@ -803,22 +1131,32 @@ export function useRegularChannelFlow(mode: RegularFlowMode = 'full') { if (!w && !h) break; } - // Success criterion is the HUB delta: the native side sweeps its close - // output into its rgb-lib wallet (RgbOutputSpender on SpendableOutputs), - // proving the split settled on-chain. The wasm-sdk has NO SpendableOutputs - // handler yet — its share stays colored on the close-tx output, invisible - // to getAssetBalance — so after the hub settles we only give the wasm - // side a short grace window before reporting the known gap. - addLog('waiting for the RGB split to settle on-chain…'); + // BOTH sides must now settle on-chain. The native side has always swept + // its close output (RgbOutputSpender on SpendableOutputs); the wasm side + // gained the same pipeline in rgb-lightning-node PR #119 ("Settle + // on-chain RGB and BTC on the wasm side after channel close"), so its + // share reaching getAssetBalance is a real expectation rather than the + // known gap it used to be — hence no grace window, and no 'partial' as a + // success shape. + // + // Two things the wasm side needs and the hub does not: + // - blocks, and plenty. The closing tx must mature past ANTI_REORG_DELAY + // (6) before SpendableOutputs fires, then the sweep tx needs its own + // confirmations. Mining 2 per poll matches upstream's own flow. + // - `future`, not `settled`. The three Balance fields are views of one + // total, not components of it (on a quiet wallet all three are equal, + // so summing two double-counts). `future` is the view that includes + // pending inflows — which is what a freshly swept close output is + // until it confirms. + addLog('waiting for the RGB split to settle on-chain (both sides)…'); const settleDeadline = Date.now() + SETTLE_TIMEOUT_S * 1000; let wasmBal: { settled?: number; future?: number; spendable?: number } | null = null; let hubSpendable = hubSpendableBefore; - let graceLeft = 5; // extra polls for the wasm side once the hub target is hit while (Date.now() < settleDeadline) { checkAbort(); - await gatewayFund(addressRef.current, 0.001, 1).catch(() => {}); + await mineBlocks(2).catch(() => {}); await sleep(POLL_INTERVAL_MS); - await wallet.listChannels().catch(() => {}); // drive beat (close coloring) + await wallet.listChannels().catch(() => {}); // drive beat (close coloring + sweep) await wallet.syncWallet().catch(() => {}); await wallet.refreshWallet().catch(() => {}); wasmBal = await wallet.getAssetBalance(CFG.assetId).catch(() => null); @@ -841,33 +1179,36 @@ export function useRegularChannelFlow(mode: RegularFlowMode = 'full') { hubSpendable, hubExpectedDelta, }); - const wasmOk = wasmSettled >= wasmExpected; + const wasmOk = wasmFuture >= wasmExpected; const hubOk = hubSpendable >= hubSpendableBefore + hubExpectedDelta; if (wasmOk && hubOk) break; - if (hubOk && --graceLeft <= 0) break; } const assets = await wallet.listAssets().catch(() => null); if (assets) addLog(`listAssets: ${JSON.stringify(assets)}`); const wasmSettled = Number(wasmBal?.settled ?? 0); + const wasmFuture = Number(wasmBal?.future ?? 0); + const wasmTotal = wasmFuture; const hubDelta = hubSpendable - hubSpendableBefore; - const wasmOk = wasmSettled >= wasmExpected; + const wasmOk = wasmTotal >= wasmExpected; const hubOk = hubDelta >= hubExpectedDelta; const kind: CloseOutcome['kind'] = wasmOk && hubOk ? 'settled' : hubOk ? 'partial' : 'incomplete'; setCloseOutcome({ kind, detail: kind === 'settled' - ? `Close settled on-chain ✓ — wasm ${wasmSettled} RGB (channel-local was ${wasmExpected}), ` + - `hub +${hubDelta} RGB (channel-local was ${hubExpectedDelta}). ` + - 'The 10-out / 5-back split survived the round trip.' + ? `Close settled on-chain ✓ — wasm ${wasmTotal} RGB (future; settled ${wasmSettled}; ` + + `channel-local was ${wasmExpected}), hub +${hubDelta} RGB ` + + `(channel-local was ${hubExpectedDelta}). The 10-out / 5-back split survived the ` + + 'round trip, and the wasm side swept its own close output.' : kind === 'partial' - ? `Hub settled on-chain ✓ (+${hubDelta} RGB, matching its channel-local ${hubExpectedDelta}) — ` + - `the split was correct. The wasm side's ${wasmExpected} RGB is colored on the close-tx ` + - 'output but the wasm-sdk has no SpendableOutputs/RGB sweep yet, so its wallet ' + - 'balance stays 0 (known gap — funds recoverable once the sweep is implemented).' - : `On-chain settle incomplete — wasm settled ${wasmSettled}/${wasmExpected}, ` + + ? `Only the hub settled (+${hubDelta} RGB, matching its channel-local ${hubExpectedDelta}) — ` + + `the split was correct, but the wasm side recovered ${wasmTotal}/${wasmExpected}. ` + + 'With the post-close sweep in place (rgb-lightning-node #119) this is a FAILURE, ' + + 'not the old known gap: check that the sweep tx was broadcast and confirmed, and ' + + 'that the closing tx matured past ANTI_REORG_DELAY (6 blocks).' + : `On-chain settle incomplete — wasm ${wasmTotal}/${wasmExpected}, ` + `hub delta ${hubDelta}/${hubExpectedDelta}. May need more blocks/refreshes; check listtransfers.`, }); addLog( @@ -938,10 +1279,12 @@ export function useRegularChannelFlow(mode: RegularFlowMode = 'full') { setReproOutcome(null); setCloseOutcome(null); setOnchain(null); + setFunding(null); + setFundingOutcome(null); }, []); const isRunning = !['idle', 'done', 'error'].includes(phase); - const busy = isRunning || reproRunning || closeRunning; + const busy = isRunning || reproRunning || closeRunning || fundingRunning; return { phase, log, @@ -960,8 +1303,20 @@ export function useRegularChannelFlow(mode: RegularFlowMode = 'full') { closeRunning, closeOutcome, onchain, + runWalletFundedOpen, + funding, + fundingOutcome, + fundingRunning, /** Keysend/close need a usable channel from a completed flow run in this tab. */ canRepro: !!channel && phase === 'done' && !busy, + /** + * Runnable with or without the main flow: it opens its own channel to a + * different peer, and bootstraps its own wallet when none exists. Running it + * cold — first click in a fresh tab — is the §6.0s experiment. + */ + canFund: !busy && !!REGULAR_PUBKEY, + /** True once a wallet exists, i.e. the next funding run will be a warm one. */ + fundWarm: !!walletRef.current, envReady: !!CFG.assetId, isRunning, }; diff --git a/src/lib/session.ts b/src/lib/session.ts index df86944..282370f 100644 --- a/src/lib/session.ts +++ b/src/lib/session.ts @@ -1,4 +1,5 @@ import { UTEXOWallet, RlnWalletManager, initRlnWasm } from '@utexo/rgb-sdk-web'; +import type { BitcoinNetwork } from '@utexo/rgb-sdk-core'; import { proxyIndexerUrl, DEMO_VSS_URL, resolveVssUrl } from './utils'; import type { WalletInstance, WalletConfig } from '../store'; @@ -106,7 +107,7 @@ async function restoreEntry(entry: SessionEntry): Promise const w = new UTEXOWallet({ mnemonic: config.mnemonic, password: config.password, - network: config.network, + network: config.network as BitcoinNetwork, proxyUrl: config.proxyUrl || undefined, transportEndpoint: config.transportEndpoint || undefined, nodeRuntimeId: config.nodeRuntimeId || undefined, @@ -145,7 +146,7 @@ async function restoreEntry(entry: SessionEntry): Promise m = await RlnWalletManager.create({ mnemonic: config.mnemonic, password: config.password, - network: config.network, + network: config.network as BitcoinNetwork, proxyUrl: config.proxyUrl || undefined, transportEndpoint: config.transportEndpoint || undefined, nodeRuntimeId: config.nodeRuntimeId || undefined, diff --git a/src/pages/HomePage.tsx b/src/pages/HomePage.tsx index 4d77299..610dd0a 100644 --- a/src/pages/HomePage.tsx +++ b/src/pages/HomePage.tsx @@ -11,7 +11,7 @@ const cards = [ { to: '/utexo-wallet', title: 'UTXO Wallet', - desc: 'Create an RLN-backed UTEXOWallet — RGB on-chain + Lightning, the full IWalletManager + IUTEXOProtocol surface', + desc: 'Create an RLN-backed UTEXOWallet — RGB on-chain + Lightning, the full IUTEXOProtocol surface', }, { to: '/lsp-apay', diff --git a/src/pages/UtexoWalletPage.tsx b/src/pages/UtexoWalletPage.tsx index 21b3548..0da0652 100644 --- a/src/pages/UtexoWalletPage.tsx +++ b/src/pages/UtexoWalletPage.tsx @@ -151,7 +151,6 @@ export function UtexoWalletPage() { const [ifaPrecision, setIfaPrecision] = useState('0'); const [ifaAmounts, setIfaAmounts] = useState('500'); const [ifaInflationAmounts, setIfaInflationAmounts] = useState('1000'); - const [ifaReplaceRights, setIfaReplaceRights] = useState('0'); const [ifaOut, setIfaOut] = useState(''); const [listOut, setListOut] = useState(''); @@ -640,7 +639,7 @@ export function UtexoWalletPage() { if (!ticker || !name || amounts.length === 0) { setIfaOut('Fill ticker, name, amounts'); return; } try { addLog('Issuing IFA ' + ticker + '...', 'info'); - const asset = await utexo.issueAssetIfa({ ticker, name, precision: parseInt(ifaPrecision) || 0, amounts, inflationAmounts, replaceRightsNum: parseInt(ifaReplaceRights) || 0, rejectListUrl: null }); + const asset = await utexo.issueAssetIfa({ ticker, name, precision: parseInt(ifaPrecision) || 0, amounts, inflationAmounts, rejectListUrl: null }); setIfaOut(json(asset)); addLog('IFA issued', 'ok'); } catch (e) { setIfaOut('Error: ' + e); addLog('Issue IFA failed: ' + e, 'err'); } @@ -787,7 +786,7 @@ export function UtexoWalletPage() { async function handleGetLnSendStatus() { if (!utexo || !lnPaymentHash.trim()) { setLnStatusOut('Enter payment hash (returned as txid by payLightningInvoice)'); return; } try { - const result = await utexo.getLightningSendRequest(lnPaymentHash.trim()); + const result = await utexo.getLightningSendStatus(lnPaymentHash.trim()); setLnStatusOut('Status: ' + json(result)); } catch (e) { setLnStatusOut('Error: ' + e); } } @@ -795,7 +794,7 @@ export function UtexoWalletPage() { async function handleGetLnReceiveStatus() { if (!utexo || !lnInvoice.trim()) { setLnStatusOut('Enter LN invoice'); return; } try { - const result = await utexo.getLightningReceiveRequest(lnInvoice.trim()); + const result = await utexo.getLightningReceiveStatus(lnInvoice.trim()); setLnStatusOut('Status: ' + json(result)); } catch (e) { setLnStatusOut('Error: ' + e); } } @@ -847,7 +846,7 @@ export function UtexoWalletPage() { if (!lnPeerAddr.trim() || !lnPeerPubkey.trim()) { setLnPeersOut('Enter peer address and pubkey'); return; } try { addLog('connectPeer...', 'info'); - await utexo.connectPeer(lnPeerAddr.trim(), lnPeerPubkey.trim()); + await utexo.connectPeer(`${lnPeerPubkey.trim()}@${lnPeerAddr.trim()}`); setLnPeersOut('Connected to ' + lnPeerPubkey.trim()); addLog('connectPeer done', 'ok'); } catch (e) { setLnPeersOut('Error: ' + e); addLog('connectPeer failed: ' + e, 'err'); } @@ -1490,9 +1489,6 @@ export function UtexoWalletPage() { setIfaInflationAmounts(e.target.value)} className={inputCls} /> - - setIfaReplaceRights(e.target.value)} className={inputCls} min="0" /> -
issueAssetIfa() @@ -1748,7 +1744,7 @@ export function UtexoWalletPage() { {/* ── LN Status & Decode ────────────────────────────────────────────── */} -
+
{utexoWarn}
@@ -1759,8 +1755,8 @@ export function UtexoWalletPage() {
- getLightningSendRequest() - getLightningReceiveRequest() + getLightningSendStatus() + getLightningReceiveStatus() decodeLnInvoice()
diff --git a/tsconfig.json b/tsconfig.json index f3a21e0..233836d 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -18,7 +18,9 @@ "noFallthroughCasesInSwitch": true, "paths": { "@utexo/rgb-sdk-core": ["../rgb-sdk-core/src/index.ts"], - "@utexo/rgb-sdk-core/*": ["../rgb-sdk-core/src/*"] + "@utexo/rgb-sdk-core/*": ["../rgb-sdk-core/src/*"], + "@utexo/rgb-sdk-web": ["../rgb-sdk-web/src/index.ts"], + "@utexo/rgb-sdk-web/*": ["../rgb-sdk-web/src/*"] } }, "include": ["src"] diff --git a/vite.config.ts b/vite.config.ts index 55310d0..66e9786 100644 --- a/vite.config.ts +++ b/vite.config.ts @@ -11,10 +11,33 @@ import { createRequire } from 'module'; const require = createRequire(import.meta.url); const __dirname = path.dirname(fileURLToPath(import.meta.url)); + +/** + * Real path of the @utexo/rln-wasm actually in use, for server.fs.allow. + * + * It is not a direct dependency of the demo — it arrives through rgb-sdk-web, + * so resolve it there. When rgb-sdk-web points at a local wasm-pack build via + * `file:`, the symlink lands outside every path below and vite refuses to serve + * the .wasm ("outside of Vite serving allow list"). + */ +const rlnWasmRoot: string[] = (() => { + for (const base of ['../rgb-sdk-web', '.']) { + const p = path.resolve(__dirname, base, 'node_modules/@utexo/rln-wasm'); + if (fs.existsSync(p)) return [fs.realpathSync(p)]; + } + return []; +})(); + const localRgbSdkCorePath = path.resolve(__dirname, '../rgb-sdk-core/src/index.ts'); -const localAliases = fs.existsSync(localRgbSdkCorePath) - ? { '@utexo/rgb-sdk-core': localRgbSdkCorePath } - : {}; +const localRgbSdkWebPath = path.resolve(__dirname, '../rgb-sdk-web/src/index.ts'); +const localAliases = { + ...(fs.existsSync(localRgbSdkCorePath) + ? { '@utexo/rgb-sdk-core': localRgbSdkCorePath } + : {}), + ...(fs.existsSync(localRgbSdkWebPath) + ? { '@utexo/rgb-sdk-web': localRgbSdkWebPath } + : {}), +}; export default defineConfig(({ mode }) => { const env = loadEnv(mode, __dirname, 'VITE_'); @@ -74,6 +97,15 @@ export default defineConfig(({ mode }) => { changeOrigin: true, rewrite: (path) => path.replace(/^\/esplora/, ''), }, + // The esplora the RLN nodes and wasm wallets actually index against + // (start-lsp-web.sh ESPLORA_PORT=3002). Same-origin so the browser can + // read /tx//status and POST /tx — the wallet-funded open probe + // needs both, and esplora sends no CORS headers. + '/indexer': { + target: env.VITE_RLN_INDEXER || 'http://127.0.0.1:3002', + changeOrigin: true, + rewrite: (path) => path.replace(/^\/indexer/, ''), + }, // utexo-lsp HTTP API (LSP/APay flows) — same-origin to avoid CORS. // VITE_LSP_BASE_URL="/lsp" (written by scripts/start-lsp-web.sh). '/lsp': { @@ -102,6 +134,13 @@ export default defineConfig(({ mode }) => { path.resolve(__dirname, '../rgb-sdk-web'), path.resolve(__dirname, '../rgb-sdk-core'), path.resolve(__dirname, '../../utexo/rgb-lightning-node/bindings/wasm-sdk/pkg'), + // Wherever @utexo/rln-wasm actually resolves to. Resolved through the + // symlink rather than hardcoded, so a `file:` dep pointing at a local + // wasm-pack build serves its .wasm without another entry here — the + // paths above only cover the two locations that happened to be used + // first, and a build anywhere else fails with "outside of Vite serving + // allow list" long after the config was last touched. + ...rlnWasmRoot, ], }, },