CLINK offers (#2518)

This commit is contained in:
ekzyis 2025-09-22 19:45:00 +02:00 committed by GitHub
parent 3df155f4f1
commit 94177af702
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
16 changed files with 418 additions and 48 deletions

View File

@ -108,6 +108,11 @@ const typeDefs = gql`
${shared}
): WalletSendWebLN!
upsertWalletRecvClink(
${shared},
noffer: String!
): WalletRecvClink!
# tests
testWalletRecvNWC(
url: String!
@ -144,6 +149,10 @@ const typeDefs = gql`
apiKey: String!
): Boolean!
testWalletRecvClink(
noffer: String!
): Boolean!
# delete
deleteWallet(id: ID!): Boolean
@ -226,6 +235,7 @@ const typeDefs = gql`
| WalletRecvLightningAddress
| WalletRecvCLNRest
| WalletRecvLNDGRPC
| WalletRecvClink
type WalletSettings {
receiveCreditsBelowSats: Int!
@ -328,6 +338,11 @@ const typeDefs = gql`
cert: String
}
type WalletRecvClink {
id: ID!
noffer: String!
}
input AutowithdrawSettings {
autoWithdrawThreshold: Int!
autoWithdrawMaxFeePercent: Float!

View File

@ -843,6 +843,27 @@ services:
CONNECT: "localhost:${LNBITS_WEB_PORT_V1}"
TORDIR: "/app/.tor"
cpu_shares: "${CPU_SHARES_LOW}"
lnpub:
image: ghcr.io/shocknet/lightning-pub@sha256:cd7bb9298d09a2cdaf1b6456ef6154e3ba24f7b902ad29cda2c08c2a4fa2af6e
container_name: lnpub
profiles:
- wallets
restart: unless-stopped
volumes:
- lnpub:/app/data
- lnd:/app/.lnd
environment:
- LND_ADDRESS=lnd:10009
- LND_CERT_PATH=/app/.lnd/tls.cert
- LND_MACAROON_PATH=/app/.lnd/data/chain/bitcoin/regtest/admin.macaroon
ports:
- ${LNPUB_PORT_1776:-1776}:1776
- ${LNPUB_PORT_1777:-1777}:1777
depends_on:
lnd:
condition: service_healthy
restart: true
cpu_shares: "${CPU_SHARES_LOW}"
dnsmasq:
image: 4km3/dnsmasq:2.90-r3
profiles:
@ -879,6 +900,7 @@ volumes:
tordata:
eclair:
dnsmasq:
lnpub:
networks:
default: {}

228
package-lock.json generated
View File

@ -20,6 +20,7 @@
"@nostr-dev-kit/ndk-wallet": "^0.5.0",
"@opensearch-project/opensearch": "^2.12.0",
"@prisma/client": "^5.20.0",
"@shocknet/clink-sdk": "^1.4.0",
"@slack/web-api": "^7.6.0",
"@svgr/webpack": "^8.1.0",
"@yudiel/react-qr-scanner": "^2.0.8",
@ -3236,11 +3237,31 @@
"url": "https://github.com/sponsors/nzakas"
}
},
"node_modules/@isaacs/balanced-match": {
"version": "4.0.1",
"resolved": "https://registry.npmjs.org/@isaacs/balanced-match/-/balanced-match-4.0.1.tgz",
"integrity": "sha512-yzMTt9lEb8Gv7zRioUilSglI0c0smZ9k5D65677DLWLtWJaXIS3CqcGyUFByYKlnUj6TkjLVs54fBl6+TiGQDQ==",
"license": "MIT",
"engines": {
"node": "20 || >=22"
}
},
"node_modules/@isaacs/brace-expansion": {
"version": "5.0.0",
"resolved": "https://registry.npmjs.org/@isaacs/brace-expansion/-/brace-expansion-5.0.0.tgz",
"integrity": "sha512-ZT55BDLV0yv0RBm2czMiZ+SqCGO7AvmOM3G/w2xhVPH+te0aKgFjmBvGlL1dH+ql2tgGO3MVrbb3jCKyvpgnxA==",
"license": "MIT",
"dependencies": {
"@isaacs/balanced-match": "^4.0.1"
},
"engines": {
"node": "20 || >=22"
}
},
"node_modules/@isaacs/cliui": {
"version": "8.0.2",
"resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz",
"integrity": "sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==",
"dev": true,
"dependencies": {
"string-width": "^5.1.2",
"string-width-cjs": "npm:string-width@^4.2.0",
@ -3257,7 +3278,6 @@
"version": "6.1.0",
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.1.0.tgz",
"integrity": "sha512-7HSX4QQb4CspciLpVFwyRe79O3xsIZDDLER21kERQ71oaPodF8jL725AgJMFAYbooIqolJoRLuM81SpeUkpkvA==",
"dev": true,
"engines": {
"node": ">=12"
},
@ -3269,7 +3289,6 @@
"version": "6.2.1",
"resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.1.tgz",
"integrity": "sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==",
"dev": true,
"engines": {
"node": ">=12"
},
@ -3280,14 +3299,12 @@
"node_modules/@isaacs/cliui/node_modules/emoji-regex": {
"version": "9.2.2",
"resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz",
"integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==",
"dev": true
"integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg=="
},
"node_modules/@isaacs/cliui/node_modules/string-width": {
"version": "5.1.2",
"resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz",
"integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==",
"dev": true,
"dependencies": {
"eastasianwidth": "^0.2.0",
"emoji-regex": "^9.2.2",
@ -3304,7 +3321,6 @@
"version": "7.1.0",
"resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz",
"integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==",
"dev": true,
"dependencies": {
"ansi-regex": "^6.0.1"
},
@ -3319,7 +3335,6 @@
"version": "8.1.0",
"resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz",
"integrity": "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==",
"dev": true,
"dependencies": {
"ansi-styles": "^6.1.0",
"string-width": "^5.0.1",
@ -5269,6 +5284,137 @@
"url": "https://paulmillr.com/funding/"
}
},
"node_modules/@shocknet/clink-sdk": {
"version": "1.4.0",
"resolved": "https://registry.npmjs.org/@shocknet/clink-sdk/-/clink-sdk-1.4.0.tgz",
"integrity": "sha512-J0PWE8CVRJrFF1Zi/UhChhvOrlmDj7LRJTpR6rbHlFPmjC5TGIW6891tVWWv+JmUR0jzez9QHFrHnc8DgIJYCQ==",
"license": "MIT",
"dependencies": {
"@noble/hashes": "^1.8.0",
"@scure/base": "^1.2.5",
"nostr-tools": "^2.13.0",
"rimraf": "^6.0.1",
"typescript": "^5.8.3"
}
},
"node_modules/@shocknet/clink-sdk/node_modules/@noble/hashes": {
"version": "1.8.0",
"resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.8.0.tgz",
"integrity": "sha512-jCs9ldd7NwzpgXDIf6P3+NrHh9/sD6CQdxHyjQI+h/6rDNo88ypBxxz45UDuZHz9r3tNz7N/VInSVoVdtXEI4A==",
"license": "MIT",
"engines": {
"node": "^14.21.3 || >=16"
},
"funding": {
"url": "https://paulmillr.com/funding/"
}
},
"node_modules/@shocknet/clink-sdk/node_modules/@scure/base": {
"version": "1.2.6",
"resolved": "https://registry.npmjs.org/@scure/base/-/base-1.2.6.tgz",
"integrity": "sha512-g/nm5FgUa//MCj1gV09zTJTaM6KBAHqLN907YVQqf7zC49+DcO4B1so4ZX07Ef10Twr6nuqYEH9GEggFXA4Fmg==",
"license": "MIT",
"funding": {
"url": "https://paulmillr.com/funding/"
}
},
"node_modules/@shocknet/clink-sdk/node_modules/glob": {
"version": "11.0.3",
"resolved": "https://registry.npmjs.org/glob/-/glob-11.0.3.tgz",
"integrity": "sha512-2Nim7dha1KVkaiF4q6Dj+ngPPMdfvLJEOpZk/jKiUAkqKebpGAWQXAq9z1xu9HKu5lWfqw/FASuccEjyznjPaA==",
"license": "ISC",
"dependencies": {
"foreground-child": "^3.3.1",
"jackspeak": "^4.1.1",
"minimatch": "^10.0.3",
"minipass": "^7.1.2",
"package-json-from-dist": "^1.0.0",
"path-scurry": "^2.0.0"
},
"bin": {
"glob": "dist/esm/bin.mjs"
},
"engines": {
"node": "20 || >=22"
},
"funding": {
"url": "https://github.com/sponsors/isaacs"
}
},
"node_modules/@shocknet/clink-sdk/node_modules/jackspeak": {
"version": "4.1.1",
"resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-4.1.1.tgz",
"integrity": "sha512-zptv57P3GpL+O0I7VdMJNBZCu+BPHVQUk55Ft8/QCJjTVxrnJHuVuX/0Bl2A6/+2oyR/ZMEuFKwmzqqZ/U5nPQ==",
"license": "BlueOak-1.0.0",
"dependencies": {
"@isaacs/cliui": "^8.0.2"
},
"engines": {
"node": "20 || >=22"
},
"funding": {
"url": "https://github.com/sponsors/isaacs"
}
},
"node_modules/@shocknet/clink-sdk/node_modules/lru-cache": {
"version": "11.2.1",
"resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.2.1.tgz",
"integrity": "sha512-r8LA6i4LP4EeWOhqBaZZjDWwehd1xUJPCJd9Sv300H0ZmcUER4+JPh7bqqZeqs1o5pgtgvXm+d9UGrB5zZGDiQ==",
"license": "ISC",
"engines": {
"node": "20 || >=22"
}
},
"node_modules/@shocknet/clink-sdk/node_modules/minimatch": {
"version": "10.0.3",
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.0.3.tgz",
"integrity": "sha512-IPZ167aShDZZUMdRk66cyQAW3qr0WzbHkPdMYa8bzZhlHhO3jALbKdxcaak7W9FfT2rZNpQuUu4Od7ILEpXSaw==",
"license": "ISC",
"dependencies": {
"@isaacs/brace-expansion": "^5.0.0"
},
"engines": {
"node": "20 || >=22"
},
"funding": {
"url": "https://github.com/sponsors/isaacs"
}
},
"node_modules/@shocknet/clink-sdk/node_modules/path-scurry": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-2.0.0.tgz",
"integrity": "sha512-ypGJsmGtdXUOeM5u93TyeIEfEhM6s+ljAhrk5vAvSx8uyY/02OvrZnA0YNGUrPXfpJMgI1ODd3nwz8Npx4O4cg==",
"license": "BlueOak-1.0.0",
"dependencies": {
"lru-cache": "^11.0.0",
"minipass": "^7.1.2"
},
"engines": {
"node": "20 || >=22"
},
"funding": {
"url": "https://github.com/sponsors/isaacs"
}
},
"node_modules/@shocknet/clink-sdk/node_modules/rimraf": {
"version": "6.0.1",
"resolved": "https://registry.npmjs.org/rimraf/-/rimraf-6.0.1.tgz",
"integrity": "sha512-9dkvaxAsk/xNXSJzMgFqqMCuFgt2+KsOFek3TMLfo8NCPfWpBmqwyNn5Y+NX56QUYfCtsyhF3ayiboEoUmJk/A==",
"license": "ISC",
"dependencies": {
"glob": "^11.0.0",
"package-json-from-dist": "^1.0.0"
},
"bin": {
"rimraf": "dist/esm/bin.mjs"
},
"engines": {
"node": "20 || >=22"
},
"funding": {
"url": "https://github.com/sponsors/isaacs"
}
},
"node_modules/@sinclair/typebox": {
"version": "0.27.8",
"resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.27.8.tgz",
@ -8308,10 +8454,10 @@
}
},
"node_modules/cross-spawn": {
"version": "7.0.3",
"resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz",
"integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==",
"dev": true,
"version": "7.0.6",
"resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz",
"integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==",
"license": "MIT",
"dependencies": {
"path-key": "^3.1.0",
"shebang-command": "^2.0.0",
@ -8993,8 +9139,7 @@
"node_modules/eastasianwidth": {
"version": "0.2.0",
"resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz",
"integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==",
"dev": true
"integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA=="
},
"node_modules/ecc-jsbn": {
"version": "0.1.2",
@ -10466,12 +10611,12 @@
}
},
"node_modules/foreground-child": {
"version": "3.3.0",
"resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.3.0.tgz",
"integrity": "sha512-Ld2g8rrAyMYFXBhEqMz8ZAHBi4J4uS1i/CxGMDnjyFWddMXLVcDp051DZfu+t7+ab7Wv6SMqpWmyFIj5UbfFvg==",
"dev": true,
"version": "3.3.1",
"resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.3.1.tgz",
"integrity": "sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw==",
"license": "ISC",
"dependencies": {
"cross-spawn": "^7.0.0",
"cross-spawn": "^7.0.6",
"signal-exit": "^4.0.1"
},
"engines": {
@ -10485,7 +10630,6 @@
"version": "4.1.0",
"resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz",
"integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==",
"dev": true,
"engines": {
"node": ">=14"
},
@ -12082,8 +12226,7 @@
"node_modules/isexe": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz",
"integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==",
"dev": true
"integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw=="
},
"node_modules/isomorphic-ws": {
"version": "5.0.0",
@ -15874,7 +16017,6 @@
"version": "7.1.2",
"resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.2.tgz",
"integrity": "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==",
"dev": true,
"engines": {
"node": ">=16 || 14 >=14.17"
}
@ -16157,19 +16299,18 @@
}
},
"node_modules/nostr-tools": {
"version": "2.8.0",
"resolved": "https://registry.npmjs.org/nostr-tools/-/nostr-tools-2.8.0.tgz",
"integrity": "sha512-aumZBa9Ok/cAJLovSBCIA/DkJjLjF/Hs5DpQGEjmyfaUkGBqd5jZjzalcVMyy/9HkkRZfJmbTPtqHTKFNvBSHQ==",
"version": "2.16.2",
"resolved": "https://registry.npmjs.org/nostr-tools/-/nostr-tools-2.16.2.tgz",
"integrity": "sha512-ZxH9EbSt5ypURZj2TGNJxZd0Omb5ag5KZSu8IyJMCdLyg2KKz+2GA0sP/cSawCQEkyviIN4eRT4G2gB/t9lMRw==",
"license": "Unlicense",
"dependencies": {
"@noble/ciphers": "^0.5.1",
"@noble/curves": "1.2.0",
"@noble/hashes": "1.3.1",
"@scure/base": "1.1.1",
"@scure/bip32": "1.3.1",
"@scure/bip39": "1.2.1"
},
"optionalDependencies": {
"nostr-wasm": "v0.1.0"
"@scure/bip39": "1.2.1",
"nostr-wasm": "0.1.0"
},
"peerDependencies": {
"typescript": ">=5.0.0"
@ -16205,8 +16346,7 @@
"node_modules/nostr-wasm": {
"version": "0.1.0",
"resolved": "https://registry.npmjs.org/nostr-wasm/-/nostr-wasm-0.1.0.tgz",
"integrity": "sha512-78BTryCLcLYv96ONU8Ws3Q1JzjlAt+43pWQhIl86xZmWeegYCNLPml7yQ+gG3vR6V5h4XGj+TxO+SS5dsThQIA==",
"optional": true
"integrity": "sha512-78BTryCLcLYv96ONU8Ws3Q1JzjlAt+43pWQhIl86xZmWeegYCNLPml7yQ+gG3vR6V5h4XGj+TxO+SS5dsThQIA=="
},
"node_modules/npm-run-path": {
"version": "4.0.1",
@ -16586,6 +16726,12 @@
"node": ">=6"
}
},
"node_modules/package-json-from-dist": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/package-json-from-dist/-/package-json-from-dist-1.0.1.tgz",
"integrity": "sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==",
"license": "BlueOak-1.0.0"
},
"node_modules/packet-reader": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/packet-reader/-/packet-reader-1.0.0.tgz",
@ -16681,7 +16827,6 @@
"version": "3.1.1",
"resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz",
"integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==",
"dev": true,
"engines": {
"node": ">=8"
}
@ -18699,7 +18844,6 @@
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz",
"integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==",
"dev": true,
"dependencies": {
"shebang-regex": "^3.0.0"
},
@ -18711,7 +18855,6 @@
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz",
"integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==",
"dev": true,
"engines": {
"node": ">=8"
}
@ -19368,7 +19511,6 @@
"version": "4.2.3",
"resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz",
"integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==",
"dev": true,
"dependencies": {
"emoji-regex": "^8.0.0",
"is-fullwidth-code-point": "^3.0.0",
@ -19488,7 +19630,6 @@
"version": "6.0.1",
"resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz",
"integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==",
"dev": true,
"dependencies": {
"ansi-regex": "^5.0.1"
},
@ -20320,9 +20461,9 @@
"integrity": "sha512-7uc1O8h1M1g0rArakJdf0uLRSSgFcYexrVoKo+bzJd32gd4gDy2L/Z+8/FjPnU9ydY3pEnVPtr9FyscYY60K1g=="
},
"node_modules/typescript": {
"version": "5.8.2",
"resolved": "https://registry.npmjs.org/typescript/-/typescript-5.8.2.tgz",
"integrity": "sha512-aJn6wq13/afZp/jT9QZmwEjDqqvSGp1VT5GVg+f/t6/oVyrgXM6BY1h9BRh/O5p3PlUPAe+WuiEZOmb/49RqoQ==",
"version": "5.9.2",
"resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.2.tgz",
"integrity": "sha512-CWBzXQrc/qOkhidw1OzBTQuYRbfyxDXJMVJ1XNwUHGROVmuaeiEm3OslpZ1RV96d7SKKjZKrSJu3+t/xlw3R9A==",
"license": "Apache-2.0",
"bin": {
"tsc": "bin/tsc",
@ -21185,7 +21326,6 @@
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz",
"integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==",
"dev": true,
"dependencies": {
"isexe": "^2.0.0"
},
@ -21538,7 +21678,6 @@
"version": "7.0.0",
"resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz",
"integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==",
"dev": true,
"dependencies": {
"ansi-styles": "^4.0.0",
"string-width": "^4.1.0",
@ -21555,7 +21694,6 @@
"version": "4.3.0",
"resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz",
"integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==",
"dev": true,
"dependencies": {
"color-convert": "^2.0.1"
},
@ -21570,7 +21708,6 @@
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
"integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==",
"dev": true,
"dependencies": {
"color-name": "~1.1.4"
},
@ -21581,8 +21718,7 @@
"node_modules/wrap-ansi-cjs/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==",
"dev": true
"integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA=="
},
"node_modules/wrap-ansi/node_modules/ansi-styles": {
"version": "4.3.0",

View File

@ -25,6 +25,7 @@
"@nostr-dev-kit/ndk-wallet": "^0.5.0",
"@opensearch-project/opensearch": "^2.12.0",
"@prisma/client": "^5.20.0",
"@shocknet/clink-sdk": "^1.4.0",
"@slack/web-api": "^7.6.0",
"@svgr/webpack": "^8.1.0",
"@yudiel/react-qr-scanner": "^2.0.8",

View File

@ -0,0 +1,31 @@
-- AlterEnum
ALTER TYPE "WalletProtocolName" ADD VALUE 'CLINK'; COMMIT;
-- AlterEnum
ALTER TYPE "WalletRecvProtocolName" ADD VALUE 'CLINK'; COMMIT;
UPDATE "WalletTemplate"
SET "recvProtocols" = array_prepend('CLINK', "recvProtocols")
WHERE "name" = 'SHOCKWALLET';
-- CreateTable
CREATE TABLE "WalletRecvClink" (
"id" SERIAL NOT NULL,
"created_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updated_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"protocolId" INTEGER NOT NULL,
"noffer" TEXT NOT NULL,
CONSTRAINT "WalletRecvClink_pkey" PRIMARY KEY ("id")
);
-- CreateIndex
CREATE UNIQUE INDEX "WalletRecvClink_protocolId_key" ON "WalletRecvClink"("protocolId");
-- AddForeignKey
ALTER TABLE "WalletRecvClink" ADD CONSTRAINT "WalletRecvClink_protocolId_fkey" FOREIGN KEY ("protocolId") REFERENCES "WalletProtocol"("id") ON DELETE CASCADE ON UPDATE CASCADE;
CREATE TRIGGER wallet_to_jsonb
AFTER INSERT OR UPDATE ON "WalletRecvClink"
FOR EACH ROW
EXECUTE PROCEDURE wallet_to_jsonb();

View File

@ -1213,6 +1213,7 @@ enum WalletProtocolName {
LNC
CLN_REST
LND_GRPC
CLINK
}
enum WalletSendProtocolName {
@ -1233,6 +1234,7 @@ enum WalletRecvProtocolName {
LN_ADDR
CLN_REST
LND_GRPC
CLINK
}
enum WalletProtocolStatus {
@ -1333,6 +1335,7 @@ model WalletProtocol {
walletRecvLightningAddress WalletRecvLightningAddress?
walletRecvCLNRest WalletRecvCLNRest?
walletRecvLNDGRPC WalletRecvLNDGRPC?
walletRecvClink WalletRecvClink?
@@unique(name: "WalletProtocol_walletId_send_name_key", [walletId, send, name])
@@index([walletId])
@ -1486,3 +1489,12 @@ model WalletRecvLNDGRPC {
macaroon String
cert String?
}
model WalletRecvClink {
id Int @id @default(autoincrement())
createdAt DateTime @default(now()) @map("created_at")
updatedAt DateTime @default(now()) @updatedAt @map("updated_at")
protocolId Int @unique
protocol WalletProtocol @relation(fields: [protocolId], references: [id], onDelete: Cascade)
noffer String
}

View File

@ -235,6 +235,20 @@ export const UPSERT_WALLET_SEND_WEBLN = gql`
}
`
export const UPSERT_WALLET_RECEIVE_CLINK = gql`
mutation upsertWalletRecvClink(
${shared.variables},
$noffer: String!
) {
upsertWalletRecvClink(
${shared.arguments},
noffer: $noffer
) {
id
}
}
`
// tests
export const TEST_WALLET_RECEIVE_NWC = gql`
@ -278,3 +292,9 @@ export const TEST_WALLET_RECEIVE_BLINK = gql`
testWalletRecvBlink(currency: $currency, apiKey: $apiKey)
}
`
export const TEST_WALLET_RECEIVE_CLINK = gql`
mutation testWalletRecvClink($noffer: String!) {
testWalletRecvClink(noffer: $noffer)
}
`

View File

@ -113,6 +113,10 @@ const WALLET_PROTOCOL_FIELDS = gql`
macaroon
cert
}
... on WalletRecvClink {
id
noffer
}
}
}
`

View File

@ -6,6 +6,7 @@ import {
UPSERT_WALLET_RECEIVE_LND_GRPC,
UPSERT_WALLET_RECEIVE_NWC,
UPSERT_WALLET_RECEIVE_PHOENIXD,
UPSERT_WALLET_RECEIVE_CLINK,
UPSERT_WALLET_SEND_BLINK,
UPSERT_WALLET_SEND_LNBITS,
UPSERT_WALLET_SEND_LNC,
@ -26,6 +27,7 @@ import {
TEST_WALLET_RECEIVE_NWC,
TEST_WALLET_RECEIVE_CLN_REST,
TEST_WALLET_RECEIVE_LND_GRPC,
TEST_WALLET_RECEIVE_CLINK,
DELETE_WALLET
} from '@/wallets/client/fragments'
import { gql, useApolloClient, useMutation, useQuery } from '@apollo/client'
@ -315,6 +317,8 @@ function protocolUpsertMutation (protocol) {
return protocol.send ? UPSERT_WALLET_SEND_LNC : NOOP_MUTATION
case 'WEBLN':
return protocol.send ? UPSERT_WALLET_SEND_WEBLN : NOOP_MUTATION
case 'CLINK':
return protocol.send ? NOOP_MUTATION : UPSERT_WALLET_RECEIVE_CLINK
default:
return NOOP_MUTATION
}
@ -338,6 +342,8 @@ function protocolTestMutation (protocol) {
return TEST_WALLET_RECEIVE_CLN_REST
case 'LND_GRPC':
return TEST_WALLET_RECEIVE_LND_GRPC
case 'CLINK':
return TEST_WALLET_RECEIVE_CLINK
default:
return NOOP_MUTATION
}

View File

@ -0,0 +1,21 @@
import { clinkValidator } from '@/wallets/lib/validate'
// CLINK: Common Lightning Interface for Nostr Keys
// https://github.com/shocknet/CLINK/
export default {
name: 'CLINK',
displayName: 'CLINK',
send: false,
fields: [
{
name: 'noffer',
label: 'noffer',
type: 'password',
placeholder: 'noffer...',
required: true,
validate: clinkValidator('noffer')
}
],
relationName: 'walletRecvClink'
}

View File

@ -0,0 +1,17 @@
Testing CLINK is done with Lightning.Pub and Shockwallet.
Shockwallet PWA: https://my.shockwallet.app/
Steps:
1. Run this command to get `nprofile` of the lnpub container
```
$ sndev logs --since 0 lnpub | grep -oE 'nprofile1\w+'
```
2. Go to https://my.shockwallet.app/sources
3. Add a new source and paste `nprofile`
4. Go to https://my.shockwallet.app/offers
5. Reload page to make sure the offer is correctly updated
6. Copy offer and paste into SN

View File

@ -7,6 +7,7 @@ import lnbitsSuite from './lnbits'
import phoenixdSuite from './phoenixd'
import blinkSuite from './blink'
import webln from './webln'
import clink from './clink'
/**
* Protocol names as used in the database
@ -45,5 +46,6 @@ export default [
...phoenixdSuite,
...lnbitsSuite,
...blinkSuite,
webln
webln,
clink
]

View File

@ -5,6 +5,7 @@ import { isInvoicableMacaroon, isInvoiceMacaroon } from '@/lib/macaroon'
import { NOSTR_PUBKEY_HEX } from '@/lib/nostr'
import { TOR_REGEXP } from '@/lib/url'
import { lightningAddressValidator } from '@/lib/validate'
import { decodeBech32 as clinkDecodeBech32, OfferPriceType } from '@shocknet/clink-sdk'
import { string, array } from 'yup'
export const externalLightningAddressValidator = lightningAddressValidator
@ -65,6 +66,35 @@ export function parseNwcUrl (walletConnectUrl) {
}
}
export const clinkValidator = (type) =>
string()
.matches(new RegExp(`^${type}1`), { message: `must start with ${type}1` })
.matches(/^(noffer|ndebit)1[02-9ac-hj-np-z]+$/, { message: 'invalid bech32 encoding' })
.test({
name: 'decode',
test: (v, context) => {
let decoded
try {
decoded = clinkDecodeBech32(v)
} catch (e) {
return context.createError({ message: `failed to decode bech32: ${e.message}` })
}
if (decoded.type !== type) {
return context.createError({ message: `must be ${type}` })
}
const { data } = decoded
if (!data) return context.createError({ message: 'no data' })
if (type === 'noffer' && data.priceType && data.priceType !== OfferPriceType.Spontaneous) {
return context.createError({ message: 'offer must be for spontaneous payments' })
}
return true
}
})
export const socketValidator = (msg = 'invalid socket') =>
string()
.test({

View File

@ -0,0 +1,49 @@
import { WALLET_CREATE_INVOICE_TIMEOUT_MS } from '@/lib/constants'
import { msatsToSats } from '@/lib/format'
import { decodeBech32, generateSecretKey, SendNofferRequest, SimplePool } from '@shocknet/clink-sdk'
export const name = 'CLINK'
// https://clinkme.dev/specs.html
const ERR_INVALID_AMOUNT = 5
export async function createInvoice (
{ msats, description, expiry },
{ noffer },
{ signal }) {
const { data: { offer, relay, pubkey } } = decodeBech32(noffer)
const pool = new SimplePool()
const sk = generateSecretKey()
const request = { offer, amount_sats: msatsToSats(msats), expires_in_seconds: expiry, description }
let response
try {
const timeout = Math.floor(WALLET_CREATE_INVOICE_TIMEOUT_MS / 1000)
// CLINK does not support a custom invoice description or expiry
response = await SendNofferRequest(pool, sk, [relay], pubkey, request, timeout)
} catch (e) {
throw typeof e === 'string' ? new Error(e) : e
} finally {
pool.close([relay])
}
if ('bolt11' in response && typeof response.bolt11 === 'string') {
return response.bolt11
}
if (response.code === ERR_INVALID_AMOUNT) {
const { min, max } = response.range
throw new Error(`invalid amount: amount must be between ${min} and ${max} sats`)
}
throw new Error(response.error)
}
export async function testCreateInvoice ({ noffer }, { signal }) {
return await createInvoice(
// lnpub minimum range seems to be 10 sats by default so we use 100 sats
{ msats: 100e3, description: 'SN test invoice', expiry: 1 },
{ noffer },
{ signal })
}

View File

@ -5,6 +5,7 @@ import * as clnRest from './clnRest'
import * as phoenixd from './phoenixd'
import * as blink from './blink'
import * as lndGrpc from './lndGrpc'
import * as clink from './clink'
export * from './util'
@ -56,5 +57,6 @@ export default [
clnRest,
phoenixd,
blink,
lndGrpc
lndGrpc,
clink
]

View File

@ -19,6 +19,8 @@ export function mapWalletResolveTypes (wallet) {
return send ? 'WalletSendCLNRest' : 'WalletRecvCLNRest'
case 'LND_GRPC':
return 'WalletRecvLNDGRPC'
case 'CLINK':
return 'WalletRecvClink'
default:
return null
}