mirror of
https://github.com/Routstr/routstrd.git
synced 2026-08-10 04:07:00 +00:00
Merge branch 'wallet-integration' of github.com-red:Routstr/routstrd
This commit is contained in:
Generated
+322
-5
@@ -9,11 +9,12 @@
|
||||
"version": "0.1.0",
|
||||
"dependencies": {
|
||||
"@cashu/cashu-ts": "^3.1.1",
|
||||
"@routstr/sdk": "^0.1.4",
|
||||
"@routstr/sdk": "^0.2.0",
|
||||
"applesauce-core": "^5.1.0",
|
||||
"applesauce-relay": "^5.1.0",
|
||||
"better-sqlite3": "^11.7.2",
|
||||
"commander": "^14.0.2",
|
||||
"qrcode": "^1.5.4",
|
||||
"rxjs": "^7.8.1",
|
||||
"zustand": "^5.0.5"
|
||||
},
|
||||
@@ -21,7 +22,8 @@
|
||||
"routstrd": "src/index.ts"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/bun": "latest"
|
||||
"@types/bun": "latest",
|
||||
"@types/qrcode": "^1.5.6"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"typescript": "^5"
|
||||
@@ -71,9 +73,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@routstr/sdk": {
|
||||
"version": "0.1.4",
|
||||
"resolved": "https://registry.npmjs.org/@routstr/sdk/-/sdk-0.1.4.tgz",
|
||||
"integrity": "sha512-4NQV7IcB1UkiO2HQa0as5OBu0v3pZ7TO265oTjMNYPtYMdYOSXjF+uPBcHCRJLfVhnTN4OOCmUQnnhYM0gR6Zw==",
|
||||
"version": "0.2.1",
|
||||
"resolved": "https://registry.npmjs.org/@routstr/sdk/-/sdk-0.2.1.tgz",
|
||||
"integrity": "sha512-sRgLQ07WX5W2NC9GRO14P8aj2rmOgvNr4/8Apazy659whxsnrmRIUNp9caaQb+9t+lyJ9NEN8RFqfqw0SntXgA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@cashu/cashu-ts": "^3.1.1",
|
||||
@@ -157,6 +159,40 @@
|
||||
"undici-types": "~7.18.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/qrcode": {
|
||||
"version": "1.5.6",
|
||||
"resolved": "https://registry.npmjs.org/@types/qrcode/-/qrcode-1.5.6.tgz",
|
||||
"integrity": "sha512-te7NQcV2BOvdj2b1hCAHzAoMNuj65kNBMz0KBaxM6c3VGBOhU0dURQKOtH8CFNI/dsKkwlv32p26qYQTWoB5bw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@types/node": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/ansi-regex": {
|
||||
"version": "5.0.1",
|
||||
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz",
|
||||
"integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/ansi-styles": {
|
||||
"version": "4.3.0",
|
||||
"resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz",
|
||||
"integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"color-convert": "^2.0.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/chalk/ansi-styles?sponsor=1"
|
||||
}
|
||||
},
|
||||
"node_modules/applesauce-core": {
|
||||
"version": "5.1.0",
|
||||
"license": "MIT",
|
||||
@@ -271,10 +307,48 @@
|
||||
"@types/node": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/camelcase": {
|
||||
"version": "5.3.1",
|
||||
"resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz",
|
||||
"integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=6"
|
||||
}
|
||||
},
|
||||
"node_modules/chownr": {
|
||||
"version": "1.1.4",
|
||||
"license": "ISC"
|
||||
},
|
||||
"node_modules/cliui": {
|
||||
"version": "6.0.0",
|
||||
"resolved": "https://registry.npmjs.org/cliui/-/cliui-6.0.0.tgz",
|
||||
"integrity": "sha512-t6wbgtoCXvAzst7QgXxJYqPt0usEfbgQdftEPbLL/cvv6HPE5VgvqCuAIDR0NgU52ds6rFwqrgakNLrHEjCbrQ==",
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"string-width": "^4.2.0",
|
||||
"strip-ansi": "^6.0.0",
|
||||
"wrap-ansi": "^6.2.0"
|
||||
}
|
||||
},
|
||||
"node_modules/color-convert": {
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
|
||||
"integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"color-name": "~1.1.4"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=7.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/color-name": {
|
||||
"version": "1.1.4",
|
||||
"resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz",
|
||||
"integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/commander": {
|
||||
"version": "14.0.3",
|
||||
"license": "MIT",
|
||||
@@ -297,6 +371,15 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/decamelize": {
|
||||
"version": "1.2.0",
|
||||
"resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz",
|
||||
"integrity": "sha512-z2S+W9X73hAUUki+N+9Za2lBlun89zigOyGrsax+KUQ6wKW4ZoWpEYBkGhQjwAjjDCkWxhY0VKEhk8wzY7F5cA==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/decompress-response": {
|
||||
"version": "6.0.0",
|
||||
"license": "MIT",
|
||||
@@ -324,6 +407,18 @@
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/dijkstrajs": {
|
||||
"version": "1.0.3",
|
||||
"resolved": "https://registry.npmjs.org/dijkstrajs/-/dijkstrajs-1.0.3.tgz",
|
||||
"integrity": "sha512-qiSlmBq9+BCdCA/L46dw8Uy93mloxsPSbwnm5yrKn2vMPiy8KyAskTF6zuV/j5BMsmOGZDPs7KjU+mjb670kfA==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/emoji-regex": {
|
||||
"version": "8.0.0",
|
||||
"resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz",
|
||||
"integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/end-of-stream": {
|
||||
"version": "1.4.5",
|
||||
"license": "MIT",
|
||||
@@ -346,10 +441,32 @@
|
||||
"version": "1.0.0",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/find-up": {
|
||||
"version": "4.1.0",
|
||||
"resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz",
|
||||
"integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"locate-path": "^5.0.0",
|
||||
"path-exists": "^4.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/fs-constants": {
|
||||
"version": "1.0.0",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/get-caller-file": {
|
||||
"version": "2.0.5",
|
||||
"resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz",
|
||||
"integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==",
|
||||
"license": "ISC",
|
||||
"engines": {
|
||||
"node": "6.* || 8.* || >= 10.*"
|
||||
}
|
||||
},
|
||||
"node_modules/github-from-package": {
|
||||
"version": "0.0.0",
|
||||
"license": "MIT"
|
||||
@@ -384,6 +501,27 @@
|
||||
"version": "1.3.8",
|
||||
"license": "ISC"
|
||||
},
|
||||
"node_modules/is-fullwidth-code-point": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz",
|
||||
"integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/locate-path": {
|
||||
"version": "5.0.0",
|
||||
"resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz",
|
||||
"integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"p-locate": "^4.1.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/mimic-response": {
|
||||
"version": "3.1.0",
|
||||
"license": "MIT",
|
||||
@@ -553,6 +691,60 @@
|
||||
"wrappy": "1"
|
||||
}
|
||||
},
|
||||
"node_modules/p-limit": {
|
||||
"version": "2.3.0",
|
||||
"resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz",
|
||||
"integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"p-try": "^2.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=6"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/sindresorhus"
|
||||
}
|
||||
},
|
||||
"node_modules/p-locate": {
|
||||
"version": "4.1.0",
|
||||
"resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz",
|
||||
"integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"p-limit": "^2.2.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/p-try": {
|
||||
"version": "2.2.0",
|
||||
"resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz",
|
||||
"integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=6"
|
||||
}
|
||||
},
|
||||
"node_modules/path-exists": {
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz",
|
||||
"integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/pngjs": {
|
||||
"version": "5.0.0",
|
||||
"resolved": "https://registry.npmjs.org/pngjs/-/pngjs-5.0.0.tgz",
|
||||
"integrity": "sha512-40QW5YalBNfQo5yRYmiw7Yz6TKKVr3h6970B2YE+3fQpsWcrbj1PzJgxeJ19DRQjhMbKPIuMY8rFaXc8moolVw==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=10.13.0"
|
||||
}
|
||||
},
|
||||
"node_modules/prebuild-install": {
|
||||
"version": "7.1.3",
|
||||
"license": "MIT",
|
||||
@@ -585,6 +777,23 @@
|
||||
"once": "^1.3.1"
|
||||
}
|
||||
},
|
||||
"node_modules/qrcode": {
|
||||
"version": "1.5.4",
|
||||
"resolved": "https://registry.npmjs.org/qrcode/-/qrcode-1.5.4.tgz",
|
||||
"integrity": "sha512-1ca71Zgiu6ORjHqFBDpnSMTR2ReToX4l1Au1VFLyVeBTFavzQnv5JxMFr3ukHVKpSrSA2MCk0lNJSykjUfz7Zg==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"dijkstrajs": "^1.0.1",
|
||||
"pngjs": "^5.0.0",
|
||||
"yargs": "^15.3.1"
|
||||
},
|
||||
"bin": {
|
||||
"qrcode": "bin/qrcode"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=10.13.0"
|
||||
}
|
||||
},
|
||||
"node_modules/rc": {
|
||||
"version": "1.2.8",
|
||||
"license": "(BSD-2-Clause OR MIT OR Apache-2.0)",
|
||||
@@ -610,6 +819,21 @@
|
||||
"node": ">= 6"
|
||||
}
|
||||
},
|
||||
"node_modules/require-directory": {
|
||||
"version": "2.1.1",
|
||||
"resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz",
|
||||
"integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/require-main-filename": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/require-main-filename/-/require-main-filename-2.0.0.tgz",
|
||||
"integrity": "sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg==",
|
||||
"license": "ISC"
|
||||
},
|
||||
"node_modules/rxjs": {
|
||||
"version": "7.8.2",
|
||||
"license": "Apache-2.0",
|
||||
@@ -645,6 +869,12 @@
|
||||
"node": ">=10"
|
||||
}
|
||||
},
|
||||
"node_modules/set-blocking": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz",
|
||||
"integrity": "sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw==",
|
||||
"license": "ISC"
|
||||
},
|
||||
"node_modules/simple-concat": {
|
||||
"version": "1.0.1",
|
||||
"funding": [
|
||||
@@ -693,6 +923,32 @@
|
||||
"safe-buffer": "~5.2.0"
|
||||
}
|
||||
},
|
||||
"node_modules/string-width": {
|
||||
"version": "4.2.3",
|
||||
"resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz",
|
||||
"integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"emoji-regex": "^8.0.0",
|
||||
"is-fullwidth-code-point": "^3.0.0",
|
||||
"strip-ansi": "^6.0.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/strip-ansi": {
|
||||
"version": "6.0.1",
|
||||
"resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz",
|
||||
"integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"ansi-regex": "^5.0.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/strip-json-comments": {
|
||||
"version": "2.0.1",
|
||||
"license": "MIT",
|
||||
@@ -759,10 +1015,71 @@
|
||||
"version": "1.0.2",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/which-module": {
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmjs.org/which-module/-/which-module-2.0.1.tgz",
|
||||
"integrity": "sha512-iBdZ57RDvnOR9AGBhML2vFZf7h8vmBjhoaZqODJBFWHVtKkDmKuHai3cx5PgVMrX5YDNp27AofYbAwctSS+vhQ==",
|
||||
"license": "ISC"
|
||||
},
|
||||
"node_modules/wrap-ansi": {
|
||||
"version": "6.2.0",
|
||||
"resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-6.2.0.tgz",
|
||||
"integrity": "sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"ansi-styles": "^4.0.0",
|
||||
"string-width": "^4.1.0",
|
||||
"strip-ansi": "^6.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/wrappy": {
|
||||
"version": "1.0.2",
|
||||
"license": "ISC"
|
||||
},
|
||||
"node_modules/y18n": {
|
||||
"version": "4.0.3",
|
||||
"resolved": "https://registry.npmjs.org/y18n/-/y18n-4.0.3.tgz",
|
||||
"integrity": "sha512-JKhqTOwSrqNA1NY5lSztJ1GrBiUodLMmIZuLiDaMRJ+itFd+ABVE8XBjOvIWL+rSqNDC74LCSFmlb/U4UZ4hJQ==",
|
||||
"license": "ISC"
|
||||
},
|
||||
"node_modules/yargs": {
|
||||
"version": "15.4.1",
|
||||
"resolved": "https://registry.npmjs.org/yargs/-/yargs-15.4.1.tgz",
|
||||
"integrity": "sha512-aePbxDmcYW++PaqBsJ+HYUFwCdv4LVvdnhBy78E57PIor8/OVvhMrADFFEDh8DHDFRv/O9i3lPhsENjO7QX0+A==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"cliui": "^6.0.0",
|
||||
"decamelize": "^1.2.0",
|
||||
"find-up": "^4.1.0",
|
||||
"get-caller-file": "^2.0.1",
|
||||
"require-directory": "^2.1.1",
|
||||
"require-main-filename": "^2.0.0",
|
||||
"set-blocking": "^2.0.0",
|
||||
"string-width": "^4.2.0",
|
||||
"which-module": "^2.0.0",
|
||||
"y18n": "^4.0.0",
|
||||
"yargs-parser": "^18.1.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/yargs-parser": {
|
||||
"version": "18.1.3",
|
||||
"resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-18.1.3.tgz",
|
||||
"integrity": "sha512-o50j0JeToy/4K6OZcaQmW6lyXXKhq7csREXcDwk2omFPJEwUNOVtJKvmDr9EI1fAJZUyZcRF7kxGBWmRXudrCQ==",
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"camelcase": "^5.0.0",
|
||||
"decamelize": "^1.2.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=6"
|
||||
}
|
||||
},
|
||||
"node_modules/zustand": {
|
||||
"version": "5.0.11",
|
||||
"license": "MIT",
|
||||
|
||||
+3
-1
@@ -16,7 +16,8 @@
|
||||
"prepublishOnly": "bun run build"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/bun": "latest"
|
||||
"@types/bun": "latest",
|
||||
"@types/qrcode": "^1.5.6"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"typescript": "^5"
|
||||
@@ -27,6 +28,7 @@
|
||||
"applesauce-core": "^5.1.0",
|
||||
"applesauce-relay": "^5.1.0",
|
||||
"commander": "^14.0.2",
|
||||
"qrcode": "^1.5.4",
|
||||
"rxjs": "^7.8.1",
|
||||
"zustand": "^5.0.5"
|
||||
}
|
||||
|
||||
+198
-8
@@ -20,6 +20,11 @@ import { logger } from "./utils/logger";
|
||||
import { setupIntegration } from "./integrations";
|
||||
import { createSdkStore } from "@routstr/sdk";
|
||||
import { createBunSqliteDriver } from "@routstr/sdk/storage";
|
||||
import * as QRCode from "qrcode";
|
||||
import {
|
||||
isCocodInstalled,
|
||||
resolveCocodExecutable,
|
||||
} from "./daemon/wallet/cocod-client";
|
||||
|
||||
type RoutstrModel = {
|
||||
id: string;
|
||||
@@ -44,6 +49,25 @@ type UsageEntry = {
|
||||
|
||||
const cliVersion = "0.1.1";
|
||||
|
||||
function parsePositiveIntOrExit(value: string, fieldName: string): number {
|
||||
const parsed = Number.parseInt(value, 10);
|
||||
if (!Number.isFinite(parsed) || parsed <= 0) {
|
||||
console.error(`Invalid ${fieldName}: ${value}`);
|
||||
process.exit(1);
|
||||
}
|
||||
return parsed;
|
||||
}
|
||||
|
||||
async function printLightningInvoice(invoice: string): Promise<void> {
|
||||
const paymentUri = `lightning:${invoice}`;
|
||||
const qr = await QRCode.toString(paymentUri, {
|
||||
type: "terminal",
|
||||
small: true,
|
||||
});
|
||||
|
||||
console.log(`${qr}\nInvoice:\n${invoice}`);
|
||||
}
|
||||
|
||||
async function initDaemon(): Promise<void> {
|
||||
logger.log("Initializing routstrd...");
|
||||
|
||||
@@ -82,10 +106,39 @@ async function initDaemon(): Promise<void> {
|
||||
logger.log(`Created config file: ${CONFIG_FILE}`);
|
||||
}
|
||||
|
||||
const config = await loadConfig();
|
||||
const cocodExecutable = resolveCocodExecutable(config.cocodPath);
|
||||
|
||||
if (!(await isCocodInstalled(config.cocodPath))) {
|
||||
if (config.cocodPath) {
|
||||
logger.error(
|
||||
`Configured cocod executable was not found: ${config.cocodPath}`,
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
logger.log("cocod not found. Installing globally with bun...");
|
||||
|
||||
const installProc = Bun.spawn(["bun", "install", "--global", "cocod"], {
|
||||
stdout: "inherit",
|
||||
stderr: "inherit",
|
||||
});
|
||||
|
||||
const installCode = await installProc.exited;
|
||||
if (installCode !== 0 || !(await isCocodInstalled(config.cocodPath))) {
|
||||
logger.error(
|
||||
"Failed to install cocod. Please run 'bun install --global cocod' manually.",
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
logger.log("cocod installed successfully.");
|
||||
}
|
||||
|
||||
console.log(`Database will be stored at: ${DB_PATH}`);
|
||||
console.log("\nInitializing cocod...");
|
||||
|
||||
const initProc = Bun.spawn(["cocod", "init"], {
|
||||
const initProc = Bun.spawn([cocodExecutable, "init"], {
|
||||
stdout: "pipe",
|
||||
stderr: "pipe",
|
||||
});
|
||||
@@ -138,7 +191,6 @@ async function initDaemon(): Promise<void> {
|
||||
logger.log("cocod initialized successfully.");
|
||||
}
|
||||
|
||||
const config = await loadConfig();
|
||||
await startDaemon({ port: String(config.port || 8008) });
|
||||
|
||||
// Create SDK store for integrations
|
||||
@@ -149,7 +201,7 @@ async function initDaemon(): Promise<void> {
|
||||
|
||||
logger.log("\nInitialization complete!");
|
||||
logger.log(
|
||||
"\n use 'cocod receive cashu' or 'cocod receive bolt11 2100' to top up your local wallet!",
|
||||
"\n use 'routstrd wallet receive cashu <token>' or 'routstrd wallet receive bolt11 2100' to top up your local wallet!",
|
||||
);
|
||||
}
|
||||
|
||||
@@ -188,13 +240,14 @@ program
|
||||
.option("--port <port>", "Port to listen on")
|
||||
.option("-p, --provider <provider>", "Default provider to use")
|
||||
.action(async (options: { port?: string; provider?: string }) => {
|
||||
if (!(await checkCocodInstalled())) {
|
||||
logger.error(
|
||||
"cocod is not installed. Run 'routstrd onboard' first to install cocod.",
|
||||
);
|
||||
const config = await loadConfig();
|
||||
if (!(await isCocodInstalled(config.cocodPath))) {
|
||||
const installHint = config.cocodPath
|
||||
? `Configured cocod executable was not found: ${config.cocodPath}`
|
||||
: "cocod is not installed. Run 'routstrd onboard' first to install cocod.";
|
||||
logger.error(installHint);
|
||||
process.exit(1);
|
||||
}
|
||||
const config = await loadConfig();
|
||||
await startDaemon({
|
||||
port: options.port || String(config.port || 8008),
|
||||
provider: options.provider,
|
||||
@@ -620,6 +673,143 @@ program
|
||||
await runUsageTui();
|
||||
});
|
||||
|
||||
const walletCmd = program.command("wallet").description("Wallet operations");
|
||||
|
||||
walletCmd
|
||||
.command("status")
|
||||
.description("Check wallet status")
|
||||
.action(async () => {
|
||||
await handleDaemonCommand("/wallet/status");
|
||||
});
|
||||
|
||||
walletCmd
|
||||
.command("unlock <passphrase>")
|
||||
.description("Unlock the wallet")
|
||||
.action(async (passphrase: string) => {
|
||||
await handleDaemonCommand("/wallet/unlock", {
|
||||
method: "POST",
|
||||
body: { passphrase },
|
||||
});
|
||||
});
|
||||
|
||||
walletCmd
|
||||
.command("balance")
|
||||
.description("Get wallet balance")
|
||||
.action(async () => {
|
||||
await handleDaemonCommand("/wallet/balance");
|
||||
});
|
||||
|
||||
const walletReceiveCmd = walletCmd
|
||||
.command("receive")
|
||||
.description("Wallet receive operations");
|
||||
|
||||
walletReceiveCmd
|
||||
.command("cashu <token>")
|
||||
.description("Receive a Cashu token")
|
||||
.action(async (token: string) => {
|
||||
await handleDaemonCommand("/wallet/receive/cashu", {
|
||||
method: "POST",
|
||||
body: { token },
|
||||
});
|
||||
});
|
||||
|
||||
walletReceiveCmd
|
||||
.command("bolt11 <amount>")
|
||||
.description("Create a Lightning invoice")
|
||||
.option("--mint-url <url>", "Mint URL to use")
|
||||
.action(async (amount: string, options: { mintUrl?: string }) => {
|
||||
try {
|
||||
await ensureDaemonRunning();
|
||||
|
||||
const result = await callDaemon("/wallet/receive/bolt11", {
|
||||
method: "POST",
|
||||
body: {
|
||||
amount: parsePositiveIntOrExit(amount, "amount"),
|
||||
mintUrl: options.mintUrl,
|
||||
},
|
||||
});
|
||||
|
||||
const output = result.output as
|
||||
| { invoice?: string; amount?: number; mintUrl?: string }
|
||||
| undefined;
|
||||
|
||||
if (typeof output?.invoice === "string" && output.invoice) {
|
||||
await printLightningInvoice(output.invoice);
|
||||
return;
|
||||
}
|
||||
|
||||
if (result.output !== undefined) {
|
||||
console.log(JSON.stringify(result.output, null, 2));
|
||||
}
|
||||
} catch (error) {
|
||||
console.error((error as Error).message);
|
||||
process.exit(1);
|
||||
}
|
||||
});
|
||||
|
||||
const walletSendCmd = walletCmd
|
||||
.command("send")
|
||||
.description("Wallet send operations");
|
||||
|
||||
walletSendCmd
|
||||
.command("cashu <amount>")
|
||||
.description("Create a Cashu token to send")
|
||||
.option("--mint-url <url>", "Mint URL to use")
|
||||
.action(async (amount: string, options: { mintUrl?: string }) => {
|
||||
await handleDaemonCommand("/wallet/send/cashu", {
|
||||
method: "POST",
|
||||
body: {
|
||||
amount: parsePositiveIntOrExit(amount, "amount"),
|
||||
mintUrl: options.mintUrl,
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
walletSendCmd
|
||||
.command("bolt11 <invoice>")
|
||||
.description("Pay a Lightning invoice")
|
||||
.option("--mint-url <url>", "Mint URL to use")
|
||||
.action(async (invoice: string, options: { mintUrl?: string }) => {
|
||||
await handleDaemonCommand("/wallet/send/bolt11", {
|
||||
method: "POST",
|
||||
body: {
|
||||
invoice,
|
||||
mintUrl: options.mintUrl,
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
const walletMintsCmd = walletCmd
|
||||
.command("mints")
|
||||
.description("Wallet mint operations");
|
||||
|
||||
walletMintsCmd
|
||||
.command("list")
|
||||
.description("List configured wallet mints")
|
||||
.action(async () => {
|
||||
await handleDaemonCommand("/wallet/mints");
|
||||
});
|
||||
|
||||
walletMintsCmd
|
||||
.command("add <url>")
|
||||
.description("Add a wallet mint")
|
||||
.action(async (url: string) => {
|
||||
await handleDaemonCommand("/wallet/mints", {
|
||||
method: "POST",
|
||||
body: { url },
|
||||
});
|
||||
});
|
||||
|
||||
walletMintsCmd
|
||||
.command("info <url>")
|
||||
.description("Get wallet mint info")
|
||||
.action(async (url: string) => {
|
||||
await handleDaemonCommand("/wallet/mints/info", {
|
||||
method: "POST",
|
||||
body: { url },
|
||||
});
|
||||
});
|
||||
|
||||
// Stop
|
||||
program
|
||||
.command("stop")
|
||||
|
||||
+726
-162
File diff suppressed because it is too large
Load Diff
+7
-3
@@ -16,6 +16,7 @@ import {
|
||||
createBunSqliteUsageTrackingDriver,
|
||||
} from "@routstr/sdk/storage";
|
||||
import { createWalletAdapter, parseBalances, runWalletCommand } from "./wallet";
|
||||
import { createCocodClient } from "./wallet/cocod-client";
|
||||
import { createModelService } from "./models";
|
||||
import { createDaemonRequestHandler } from "./http";
|
||||
import { RoutstrClient } from "@routstr/sdk";
|
||||
@@ -48,7 +49,11 @@ async function main(): Promise<void> {
|
||||
const { ensureProvidersBootstrapped, getRoutstr21Models } =
|
||||
createModelService(modelManager);
|
||||
|
||||
const walletAdapter = await createWalletAdapter();
|
||||
const walletClient = createCocodClient({ cocodPath: config.cocodPath });
|
||||
const walletAdapter = await createWalletAdapter({
|
||||
cocodPath: config.cocodPath,
|
||||
walletClient,
|
||||
});
|
||||
|
||||
const server = createServer();
|
||||
server.on(
|
||||
@@ -57,6 +62,7 @@ async function main(): Promise<void> {
|
||||
provider,
|
||||
server,
|
||||
store,
|
||||
walletClient,
|
||||
walletAdapter,
|
||||
storageAdapter,
|
||||
providerRegistry,
|
||||
@@ -64,8 +70,6 @@ async function main(): Promise<void> {
|
||||
modelManager,
|
||||
ensureProvidersBootstrapped,
|
||||
getRoutstr21Models,
|
||||
runWalletCommand,
|
||||
parseBalances,
|
||||
mode: config.mode || "apikeys",
|
||||
usageTrackingDriver,
|
||||
}),
|
||||
|
||||
@@ -0,0 +1,267 @@
|
||||
import { existsSync } from "fs";
|
||||
import { logger } from "../../utils/logger";
|
||||
|
||||
const DEFAULT_CONFIG_DIR = `${process.env.HOME || process.env.USERPROFILE || ""}/.cocod`;
|
||||
const DEFAULT_SOCKET_PATH =
|
||||
process.env.COCOD_SOCKET || `${DEFAULT_CONFIG_DIR}/cocod.sock`;
|
||||
|
||||
type UnixRequestInit = RequestInit & { unix: string };
|
||||
|
||||
type CommandResponse<T> = {
|
||||
output?: T;
|
||||
error?: string;
|
||||
};
|
||||
|
||||
type CocodFetch = (
|
||||
input: string | URL | Request,
|
||||
init?: UnixRequestInit,
|
||||
) => Promise<Response>;
|
||||
|
||||
type SpawnedProcess = {
|
||||
exited: Promise<number>;
|
||||
unref?: () => void;
|
||||
};
|
||||
|
||||
type SpawnDaemon = (args: string[], env: Record<string, string>) => SpawnedProcess;
|
||||
|
||||
export type CocodState = "UNINITIALIZED" | "LOCKED" | "UNLOCKED" | "ERROR";
|
||||
|
||||
export type CocodBalanceOutput = Record<string, { sats?: number } | number>;
|
||||
|
||||
export class CocodHttpError extends Error {
|
||||
status: number;
|
||||
|
||||
constructor(status: number, message: string) {
|
||||
super(message);
|
||||
this.name = "CocodHttpError";
|
||||
this.status = status;
|
||||
}
|
||||
}
|
||||
|
||||
export interface CocodClient {
|
||||
ping(): Promise<boolean>;
|
||||
getStatus(): Promise<CocodState>;
|
||||
unlock(passphrase: string): Promise<string>;
|
||||
getBalances(): Promise<Record<string, number>>;
|
||||
receiveCashu(token: string): Promise<string>;
|
||||
receiveBolt11(amount: number, mintUrl?: string): Promise<string>;
|
||||
sendCashu(amount: number, mintUrl?: string): Promise<string>;
|
||||
sendBolt11(invoice: string, mintUrl?: string): Promise<string>;
|
||||
listMints(): Promise<string[]>;
|
||||
addMint(url: string): Promise<string>;
|
||||
getMintInfo(url: string): Promise<unknown>;
|
||||
}
|
||||
|
||||
export function resolveCocodExecutable(
|
||||
cocodPath?: string | null,
|
||||
): string {
|
||||
const trimmed = cocodPath?.trim();
|
||||
return trimmed || "cocod";
|
||||
}
|
||||
|
||||
export async function isCocodInstalled(
|
||||
cocodPath?: string | null,
|
||||
): Promise<boolean> {
|
||||
const executable = resolveCocodExecutable(cocodPath);
|
||||
|
||||
if (executable.includes("/")) {
|
||||
return existsSync(executable);
|
||||
}
|
||||
|
||||
try {
|
||||
const proc = Bun.spawn({
|
||||
cmd: ["which", executable],
|
||||
stdout: "ignore",
|
||||
stderr: "ignore",
|
||||
});
|
||||
return (await proc.exited) === 0;
|
||||
} catch {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
export function normalizeBalances(
|
||||
output: CocodBalanceOutput | undefined,
|
||||
): Record<string, number> {
|
||||
if (!output) return {};
|
||||
|
||||
return Object.fromEntries(
|
||||
Object.entries(output).map(([mintUrl, value]) => {
|
||||
if (typeof value === "number") {
|
||||
return [mintUrl, value];
|
||||
}
|
||||
return [mintUrl, Number(value?.sats ?? 0)];
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
function parseMintList(output: string | undefined): string[] {
|
||||
return (output || "")
|
||||
.split("\n")
|
||||
.map((line) => line.trim())
|
||||
.filter(Boolean);
|
||||
}
|
||||
|
||||
function delay(ms: number): Promise<void> {
|
||||
return new Promise((resolve) => setTimeout(resolve, ms));
|
||||
}
|
||||
|
||||
export function createCocodClient(options: {
|
||||
cocodPath?: string | null;
|
||||
socketPath?: string;
|
||||
fetchImpl?: CocodFetch;
|
||||
spawnDaemon?: SpawnDaemon;
|
||||
pollIntervalMs?: number;
|
||||
startupTimeoutMs?: number;
|
||||
} = {}): CocodClient {
|
||||
const executable = resolveCocodExecutable(options.cocodPath);
|
||||
const socketPath = options.socketPath || DEFAULT_SOCKET_PATH;
|
||||
const fetchImpl = options.fetchImpl || (fetch as CocodFetch);
|
||||
const pollIntervalMs = options.pollIntervalMs ?? 100;
|
||||
const startupTimeoutMs = options.startupTimeoutMs ?? 5000;
|
||||
|
||||
const spawnDaemon: SpawnDaemon =
|
||||
options.spawnDaemon ||
|
||||
((args, env) => {
|
||||
const proc = Bun.spawn(args, {
|
||||
stdin: "ignore",
|
||||
stdout: "ignore",
|
||||
stderr: "ignore",
|
||||
detached: true,
|
||||
env,
|
||||
});
|
||||
proc.unref();
|
||||
return proc;
|
||||
});
|
||||
|
||||
let startPromise: Promise<void> | null = null;
|
||||
|
||||
async function fetchJson<T>(
|
||||
path: string,
|
||||
init: Omit<UnixRequestInit, "unix"> = {},
|
||||
): Promise<CommandResponse<T>> {
|
||||
const requestInit: UnixRequestInit = {
|
||||
...init,
|
||||
unix: socketPath,
|
||||
};
|
||||
|
||||
const response = await fetchImpl(`http://localhost${path}`, requestInit);
|
||||
const data = (await response.json()) as CommandResponse<T>;
|
||||
|
||||
if (!response.ok) {
|
||||
throw new CocodHttpError(
|
||||
response.status,
|
||||
data.error || response.statusText || `HTTP ${response.status}`,
|
||||
);
|
||||
}
|
||||
|
||||
return data;
|
||||
}
|
||||
|
||||
async function pingInternal(): Promise<boolean> {
|
||||
try {
|
||||
await fetchJson<string>("/ping");
|
||||
return true;
|
||||
} catch {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
async function startDaemon(): Promise<void> {
|
||||
const env = { ...process.env, COCOD_SOCKET: socketPath };
|
||||
const proc = spawnDaemon([executable, "daemon"], env);
|
||||
const maxPolls = Math.ceil(startupTimeoutMs / pollIntervalMs);
|
||||
let exitCode: number | null = null;
|
||||
|
||||
void proc.exited.then((code) => {
|
||||
exitCode = code;
|
||||
});
|
||||
|
||||
for (let i = 0; i < maxPolls; i++) {
|
||||
await delay(pollIntervalMs);
|
||||
|
||||
if (exitCode !== null) {
|
||||
throw new Error(`cocod daemon exited early with code ${exitCode}`);
|
||||
}
|
||||
|
||||
if (await pingInternal()) {
|
||||
logger.log(`Connected to cocod daemon on ${socketPath}`);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
throw new Error(
|
||||
`cocod daemon failed to start within ${Math.round(startupTimeoutMs / 1000)} seconds`,
|
||||
);
|
||||
}
|
||||
|
||||
async function ensureDaemonRunning(): Promise<void> {
|
||||
if (await pingInternal()) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!startPromise) {
|
||||
logger.log(`Starting cocod daemon via ${executable}...`);
|
||||
startPromise = startDaemon().finally(() => {
|
||||
startPromise = null;
|
||||
});
|
||||
}
|
||||
|
||||
await startPromise;
|
||||
}
|
||||
|
||||
async function callDaemon<T>(
|
||||
path: string,
|
||||
init: Omit<UnixRequestInit, "unix"> = {},
|
||||
): Promise<T> {
|
||||
await ensureDaemonRunning();
|
||||
const response = await fetchJson<T>(path, init);
|
||||
return response.output as T;
|
||||
}
|
||||
|
||||
function post<T>(path: string, body: Record<string, unknown>): Promise<T> {
|
||||
return callDaemon<T>(path, {
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify(body),
|
||||
});
|
||||
}
|
||||
|
||||
return {
|
||||
async ping(): Promise<boolean> {
|
||||
return pingInternal();
|
||||
},
|
||||
async getStatus(): Promise<CocodState> {
|
||||
return callDaemon<CocodState>("/status");
|
||||
},
|
||||
async unlock(passphrase: string): Promise<string> {
|
||||
return post<string>("/unlock", { passphrase });
|
||||
},
|
||||
async getBalances(): Promise<Record<string, number>> {
|
||||
const output = await callDaemon<CocodBalanceOutput>("/balance");
|
||||
return normalizeBalances(output);
|
||||
},
|
||||
async receiveCashu(token: string): Promise<string> {
|
||||
return post<string>("/receive/cashu", { token });
|
||||
},
|
||||
async receiveBolt11(amount: number, mintUrl?: string): Promise<string> {
|
||||
return post<string>("/receive/bolt11", { amount, mintUrl });
|
||||
},
|
||||
async sendCashu(amount: number, mintUrl?: string): Promise<string> {
|
||||
return post<string>("/send/cashu", { amount, mintUrl });
|
||||
},
|
||||
async sendBolt11(invoice: string, mintUrl?: string): Promise<string> {
|
||||
return post<string>("/send/bolt11", { invoice, mintUrl });
|
||||
},
|
||||
async listMints(): Promise<string[]> {
|
||||
const output = await callDaemon<string>("/mints/list");
|
||||
return parseMintList(output);
|
||||
},
|
||||
async addMint(url: string): Promise<string> {
|
||||
return post<string>("/mints/add", { url });
|
||||
},
|
||||
async getMintInfo(url: string): Promise<unknown> {
|
||||
return post<unknown>("/mints/info", { url });
|
||||
},
|
||||
};
|
||||
}
|
||||
+54
-138
@@ -1,119 +1,52 @@
|
||||
import { spawn } from "child_process";
|
||||
import { getDecodedToken } from "@cashu/cashu-ts";
|
||||
import { logger } from "../../utils/logger";
|
||||
import { createCocodClient, type CocodClient } from "./cocod-client";
|
||||
|
||||
export async function runWalletCommand(args: string[]): Promise<string> {
|
||||
return new Promise((resolve, reject) => {
|
||||
const child = spawn("cocod", args, {
|
||||
stdio: ["ignore", "pipe", "pipe"],
|
||||
});
|
||||
let stdout = "";
|
||||
let stderr = "";
|
||||
|
||||
child.stdout.on("data", (chunk) => {
|
||||
stdout += chunk.toString();
|
||||
});
|
||||
child.stderr.on("data", (chunk) => {
|
||||
stderr += chunk.toString();
|
||||
});
|
||||
child.on("error", (error) => reject(error));
|
||||
child.on("close", (code) => {
|
||||
if (code && code !== 0) {
|
||||
reject(
|
||||
new Error(stderr.trim() || stdout.trim() || "Wallet CLI failed"),
|
||||
);
|
||||
return;
|
||||
}
|
||||
resolve(stdout.trim());
|
||||
});
|
||||
});
|
||||
export function decodeCashuTokenAmount(token: string): {
|
||||
amount: number;
|
||||
unit: "sat" | "msat";
|
||||
} {
|
||||
const decoded = getDecodedToken(token);
|
||||
const amount =
|
||||
decoded?.proofs?.reduce((sum, proof) => sum + proof.amount, 0) ?? 0;
|
||||
const unit = decoded?.unit === "msat" ? "msat" : "sat";
|
||||
return { amount, unit };
|
||||
}
|
||||
|
||||
export function parseBalances(output: string): Record<string, number> {
|
||||
const trimmed = output.trim();
|
||||
if (!trimmed) return {};
|
||||
|
||||
try {
|
||||
const parsed = JSON.parse(trimmed) as Record<
|
||||
string,
|
||||
{ sats?: number } | number
|
||||
>;
|
||||
if (parsed && typeof parsed === "object") {
|
||||
return Object.fromEntries(
|
||||
Object.entries(parsed).map(([mintUrl, value]) => {
|
||||
if (typeof value === "number") {
|
||||
return [mintUrl, value];
|
||||
}
|
||||
if (value && typeof value === "object" && "sats" in value) {
|
||||
return [mintUrl, Number(value.sats ?? 0)];
|
||||
}
|
||||
return [mintUrl, 0];
|
||||
}),
|
||||
);
|
||||
}
|
||||
} catch {
|
||||
// Fall back to line parsing.
|
||||
}
|
||||
|
||||
const balances: Record<string, number> = {};
|
||||
trimmed
|
||||
.split("\n")
|
||||
.map((line) => line.trim())
|
||||
.forEach((line) => {
|
||||
const match = line.match(/^(\S+):\s+(\d+)\s+s$/);
|
||||
const mintUrl = match?.[1];
|
||||
const amount = match?.[2];
|
||||
if (mintUrl && amount) {
|
||||
balances[mintUrl] = Number.parseInt(amount, 10);
|
||||
}
|
||||
});
|
||||
return balances;
|
||||
}
|
||||
|
||||
export function parseMints(
|
||||
output: string,
|
||||
): Array<{ url: string; trusted: boolean }> {
|
||||
return output
|
||||
.split("\n")
|
||||
.map((line) => line.trim())
|
||||
.map((line) => {
|
||||
const urlMatch = line.match(/https?:\/\/\S+/i);
|
||||
if (!urlMatch) return null;
|
||||
const trustedMatch = line.match(/trusted:\s*(true|false)/i);
|
||||
const trustedValue = trustedMatch?.[1];
|
||||
return {
|
||||
url: urlMatch[0],
|
||||
trusted: trustedMatch ? trustedValue?.toLowerCase() === "true" : false,
|
||||
};
|
||||
})
|
||||
.filter((entry): entry is { url: string; trusted: boolean } =>
|
||||
Boolean(entry),
|
||||
);
|
||||
}
|
||||
|
||||
export function pickTokenLine(output: string): string {
|
||||
const lines = output
|
||||
.split("\n")
|
||||
.map((line) => line.trim())
|
||||
.filter(Boolean);
|
||||
return lines[lines.length - 1] || "";
|
||||
}
|
||||
|
||||
export async function createWalletAdapter() {
|
||||
export async function createWalletAdapter(options: {
|
||||
cocodPath?: string | null;
|
||||
walletClient?: CocodClient;
|
||||
} = {}) {
|
||||
const client =
|
||||
options.walletClient || createCocodClient({ cocodPath: options.cocodPath });
|
||||
let activeMintUrl: string | null = null;
|
||||
let mintUnits: Record<string, "sat" | "msat"> = {};
|
||||
|
||||
async function syncMintState(
|
||||
balances?: Record<string, number>,
|
||||
): Promise<Record<string, number>> {
|
||||
const nextBalances = balances || (await client.getBalances());
|
||||
|
||||
mintUnits = Object.fromEntries(
|
||||
Object.keys(nextBalances).map((mintUrl) => [mintUrl, "sat"]),
|
||||
);
|
||||
|
||||
try {
|
||||
const mints = await client.listMints();
|
||||
activeMintUrl = mints[0] || Object.keys(nextBalances)[0] || null;
|
||||
} catch (error) {
|
||||
logger.error("Failed to list cocod mints:", error);
|
||||
if (!activeMintUrl) {
|
||||
activeMintUrl = Object.keys(nextBalances)[0] || null;
|
||||
}
|
||||
}
|
||||
|
||||
return nextBalances;
|
||||
}
|
||||
|
||||
const walletAdapter = {
|
||||
async getBalances(): Promise<Record<string, number>> {
|
||||
const output = await runWalletCommand(["balance"]);
|
||||
const balances = parseBalances(output);
|
||||
mintUnits = Object.fromEntries(
|
||||
Object.keys(balances).map((mintUrl) => [mintUrl, "sat"]),
|
||||
);
|
||||
if (!activeMintUrl) {
|
||||
activeMintUrl = Object.keys(balances)[0] || null;
|
||||
}
|
||||
return balances;
|
||||
return syncMintState();
|
||||
},
|
||||
getMintUnits(): Record<string, "sat" | "msat"> {
|
||||
return mintUnits;
|
||||
@@ -128,18 +61,7 @@ export async function createWalletAdapter() {
|
||||
|
||||
for (let attempt = 0; attempt <= maxRetries; attempt++) {
|
||||
try {
|
||||
const output = await runWalletCommand([
|
||||
"send",
|
||||
"cashu",
|
||||
String(amount),
|
||||
"--mint-url",
|
||||
mintUrl,
|
||||
]);
|
||||
const token = pickTokenLine(output);
|
||||
if (!token) {
|
||||
throw new Error("Wallet CLI did not return a token.");
|
||||
}
|
||||
return token;
|
||||
return await client.sendCashu(amount, mintUrl);
|
||||
} catch (error) {
|
||||
const errorMessage =
|
||||
error instanceof Error ? error.message : String(error);
|
||||
@@ -152,9 +74,7 @@ export async function createWalletAdapter() {
|
||||
logger.log(
|
||||
`sendToken attempt ${attempt + 1} failed with reserved proof error, retrying in ${retryDelayMs / 1000}s...`,
|
||||
);
|
||||
await new Promise((resolve) =>
|
||||
setTimeout(resolve, retryDelayMs),
|
||||
);
|
||||
await new Promise((resolve) => setTimeout(resolve, retryDelayMs));
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -162,6 +82,7 @@ export async function createWalletAdapter() {
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
throw new Error("sendToken failed after max retries");
|
||||
},
|
||||
async receiveToken(token: string): Promise<{
|
||||
@@ -171,16 +92,10 @@ export async function createWalletAdapter() {
|
||||
message?: string;
|
||||
}> {
|
||||
try {
|
||||
await runWalletCommand(["receive", "cashu", token]);
|
||||
const decoded = getDecodedToken(token);
|
||||
const amount = decoded?.proofs?.reduce(
|
||||
(sum, proof) => sum + proof.amount,
|
||||
0,
|
||||
);
|
||||
const unit = decoded?.unit === "msat" ? "msat" : "sat";
|
||||
return { success: true, amount: amount ?? 0, unit };
|
||||
await client.receiveCashu(token);
|
||||
const { amount, unit } = decodeCashuTokenAmount(token);
|
||||
return { success: true, amount, unit };
|
||||
} catch (error) {
|
||||
console.log("Eerro in receive", error);
|
||||
const errorMessage =
|
||||
error instanceof Error ? error.message : String(error);
|
||||
const message = errorMessage.includes("Failed to fetch mint")
|
||||
@@ -189,18 +104,19 @@ export async function createWalletAdapter() {
|
||||
return { success: false, amount: 0, unit: "sat", message };
|
||||
}
|
||||
},
|
||||
isUsingNip60(): boolean {
|
||||
return false;
|
||||
},
|
||||
};
|
||||
|
||||
try {
|
||||
const mintsOutput = await runWalletCommand(["mints", "list"]);
|
||||
const mints = parseMints(mintsOutput);
|
||||
activeMintUrl =
|
||||
mints.find((mint) => mint.trusted)?.url || mints[0]?.url || null;
|
||||
const [balances, mints] = await Promise.all([
|
||||
client.getBalances(),
|
||||
client.listMints().catch(() => []),
|
||||
]);
|
||||
mintUnits = Object.fromEntries(
|
||||
Object.keys(balances).map((mintUrl) => [mintUrl, "sat"]),
|
||||
);
|
||||
activeMintUrl = mints[0] || Object.keys(balances)[0] || null;
|
||||
} catch (error) {
|
||||
logger.error("Failed to read mints from wallet:", error);
|
||||
logger.error("Failed to initialize wallet adapter state:", error);
|
||||
}
|
||||
|
||||
return walletAdapter;
|
||||
|
||||
Reference in New Issue
Block a user