diff --git a/api/resolvers/wallet.js b/api/resolvers/wallet.js
index e7d974934..a69860de9 100644
--- a/api/resolvers/wallet.js
+++ b/api/resolvers/wallet.js
@@ -7,7 +7,7 @@ import lnpr from 'bolt11'
import { SELECT } from './item'
import { lnAddrOptions } from '../../lib/lnurl'
import { msatsToSats, msatsToSatsDecimal, ensureB64 } from '../../lib/format'
-import { LNDAutowithdrawSchema, amountSchema, lnAddrAutowithdrawSchema, lnAddrSchema, ssValidate, withdrawlSchema } from '../../lib/validate'
+import { LNDAutowithdrawSchema, amountSchema, lnAddrAutowithdrawSchema, lnAddrSchema, ssValidate, withdrawlSchema, CoreLightningAutowithdrawSchema } from '../../lib/validate'
import { ANON_BALANCE_LIMIT_MSATS, ANON_INV_PENDING_LIMIT, ANON_USER_ID, BALANCE_LIMIT_MSATS, INVOICE_RETENTION_DAYS, INV_PENDING_LIMIT, USER_IDS_BALANCE_NO_LIMIT } from '../../lib/constants'
import { datePivot } from '../../lib/time'
import assertGofacYourself from './ofac'
@@ -304,7 +304,13 @@ export default {
},
WalletDetails: {
__resolveType (wallet) {
- return wallet.address ? 'WalletLNAddr' : 'WalletLND'
+ if (wallet.address) {
+ return 'WalletLNAddr'
+ } else if (wallet.type === 'LND') {
+ return 'WalletLND'
+ } else {
+ return 'WalletCoreLightning'
+ }
}
},
Mutation: {
@@ -432,6 +438,29 @@ export default {
},
{ settings, data }, { me, models })
},
+
+ upsertWalletCoreLightning: async (parent, { settings, ...data }, { me, models }) => {
+ return await upsertWallet(
+ {
+ schema: CoreLightningAutowithdrawSchema,
+ walletName: 'walletCoreLightning',
+ walletType: 'CORE_LIGHTNING',
+ testConnect: async ({ rune, socket }) => {
+ const options = {
+ method: 'POST',
+ headers: {
+ 'content-type': 'application/json',
+ Rune: rune
+ },
+ body: JSON.stringify({ amount_msat: 0, label: 'SN connection test', description: 'SN connection test' })
+ }
+
+ return await fetch(`${socket}/v1/invoice`, options)
+ }
+ },
+ { settings, data }, { me, models })
+ },
+
upsertWalletLNAddr: async (parent, { settings, ...data }, { me, models }) => {
return await upsertWallet(
{
diff --git a/api/typeDefs/wallet.js b/api/typeDefs/wallet.js
index c2cca6aef..7bf4d5304 100644
--- a/api/typeDefs/wallet.js
+++ b/api/typeDefs/wallet.js
@@ -19,6 +19,7 @@ export default gql`
cancelInvoice(hash: String!, hmac: String!): Invoice!
dropBolt11(id: ID): Withdrawl
upsertWalletLND(id: ID, socket: String!, macaroon: String!, cert: String, settings: AutowithdrawSettings!): Boolean
+ upsertWalletCoreLightning(id: ID, socket: String!, rune: String!, settings: AutowithdrawSettings!): Boolean
upsertWalletLNAddr(id: ID, address: String!, settings: AutowithdrawSettings!): Boolean
removeWallet(id: ID!): Boolean
}
@@ -41,7 +42,12 @@ export default gql`
cert: String
}
- union WalletDetails = WalletLNAddr | WalletLND
+ type WalletCoreLightning {
+ socket: String!
+ rune: String!
+ }
+
+ union WalletDetails = WalletLNAddr | WalletLND | WalletCoreLightning
input AutowithdrawSettings {
autoWithdrawThreshold: Int!
diff --git a/fragments/wallet.js b/fragments/wallet.js
index f1a3c09e3..1bf3e4048 100644
--- a/fragments/wallet.js
+++ b/fragments/wallet.js
@@ -89,6 +89,13 @@ mutation upsertWalletLND($id: ID, $socket: String!, $macaroon: String!, $cert: S
}
`
+export const UPSERT_WALLET_CORE_LIGHTNING =
+gql`
+mutation upsertWalletCoreLightning($id: ID, $socket: String!, $rune: String!, $settings: AutowithdrawSettings!) {
+ upsertWalletCoreLightning(id: $id, socket: $socket, rune: $rune, settings: $settings)
+}
+`
+
export const REMOVE_WALLET =
gql`
mutation removeWallet($id: ID!) {
@@ -113,6 +120,10 @@ export const WALLET = gql`
macaroon
cert
}
+ ... on WalletCoreLightning {
+ socket
+ rune
+ }
}
}
}
@@ -135,6 +146,10 @@ export const WALLET_BY_TYPE = gql`
macaroon
cert
}
+ ... on WalletCoreLightning {
+ socket
+ rune
+ }
}
}
}
diff --git a/lib/validate.js b/lib/validate.js
index 571741e94..77cb596d0 100644
--- a/lib/validate.js
+++ b/lib/validate.js
@@ -329,6 +329,39 @@ export function LNDAutowithdrawSchema ({ me } = {}) {
})
}
+async function isInvoiceOnlyRune (socket, rune) {
+ const url = 'https://cln-regtest-demo.blockstream.com/v1/decode'
+ const options = {
+ method: 'POST',
+ headers: {
+ 'content-type': 'application/json',
+ Rune: 'YuJqHOZLvCCiL0bY3mT75V_KX_dYNdhTlspcAkGZL5w9MA=='
+ },
+ body: JSON.stringify({ string: 'zwcfm5tup11kf2fEObNDDPAcbNE-c_0m4KzbOpvt9gs9MTQmbWV0aG9kPWludm9pY2U=' })
+ }
+
+ fetch(url, options).then(res => res.json()).then((response) => {
+ const requiredResponse = 'method (of command) equal to \'invoice\''
+ if (!response.restrictions && requiredResponse !== response.restrictions[0].alternatives[0].summary && response.restrictions.length > 1) {
+ return false
+ } else {
+ return true
+ }
+ })
+}
+
+export function CoreLightningAutowithdrawSchema ({ me, socket } = {}) {
+ return object({
+ socket: string().socket().required('required'),
+ rune: hexOrBase64Validator.required('required').test({
+ name: 'rune',
+ test: v => isInvoiceOnlyRune(string().socket(), v),
+ message: 'rune is not for invoice only'
+ }),
+ ...autowithdrawSchemaMembers({ me })
+ })
+}
+
export function autowithdrawSchemaMembers ({ me } = {}) {
return {
priority: boolean(),
diff --git a/pages/settings/wallets/core-lightning.js b/pages/settings/wallets/core-lightning.js
new file mode 100644
index 000000000..b51b0477d
--- /dev/null
+++ b/pages/settings/wallets/core-lightning.js
@@ -0,0 +1,114 @@
+import { getGetServerSideProps } from '../../../api/ssrApollo'
+import { Form, Input } from '../../../components/form'
+import { CenterLayout } from '../../../components/layout'
+import { useMe } from '../../../components/me'
+import { WalletButtonBar, WalletCard } from '../../../components/wallet-card'
+import { useMutation } from '@apollo/client'
+import { useToast } from '../../../components/toast'
+import { CoreLightningAutowithdrawSchema } from '../../../lib/validate'
+import { useRouter } from 'next/router'
+import { AutowithdrawSettings, autowithdrawInitial } from '../../../components/autowithdraw-shared'
+import { REMOVE_WALLET, UPSERT_WALLET_CORE_LIGHTNING, WALLET_BY_TYPE } from '../../../fragments/wallet'
+import Info from '../../../components/info'
+import Text from '../../../components/text'
+
+const variables = { type: 'CORE_LIGHTNING' }
+export const getServerSideProps = getGetServerSideProps({ query: WALLET_BY_TYPE, variables, authRequired: true })
+
+export default function CoreLightning ({ ssrData }) {
+ const me = useMe()
+ const toaster = useToast()
+ const router = useRouter()
+ const [upsertWalletCoreLightning] = useMutation(UPSERT_WALLET_CORE_LIGHTNING)
+ const [removeWallet] = useMutation(REMOVE_WALLET)
+
+ const { walletByType: wallet } = ssrData || {}
+
+ return (
+
+ Core Lightning
+ autowithdraw to your Core Lightning node
+ You must have CLNRest working on your node. More info here.
+
+
+
+ )
+}
+
+export function CoreLightningCard ({ wallet }) {
+ return (
+
+ )
+}
diff --git a/pages/settings/wallets/index.js b/pages/settings/wallets/index.js
index a9ffe44ce..1019cb575 100644
--- a/pages/settings/wallets/index.js
+++ b/pages/settings/wallets/index.js
@@ -9,6 +9,7 @@ import { LNDCard } from './lnd'
import { WALLETS } from '../../../fragments/wallet'
import { useQuery } from '@apollo/client'
import PageLoading from '../../../components/page-loading'
+import { CoreLightningCard } from './core-lightning'
export const getServerSideProps = getGetServerSideProps({ query: WALLETS, authRequired: true })
@@ -19,6 +20,7 @@ export default function Wallet ({ ssrData }) {
const { wallets } = data || ssrData
const lnd = wallets.find(w => w.type === 'LND')
const lnaddr = wallets.find(w => w.type === 'LIGHTNING_ADDRESS')
+ const coreLightning = wallets.find(w => w.type === 'CORE_LIGHTNING')
return (
@@ -30,7 +32,7 @@ export default function Wallet ({ ssrData }) {
-
+
diff --git a/prisma/migrations/20240216221439_add_core_lightning_to_attached_wallets/migration.sql b/prisma/migrations/20240216221439_add_core_lightning_to_attached_wallets/migration.sql
new file mode 100644
index 000000000..e50221ab6
--- /dev/null
+++ b/prisma/migrations/20240216221439_add_core_lightning_to_attached_wallets/migration.sql
@@ -0,0 +1,20 @@
+ALTER TYPE "WalletType" ADD VALUE 'CORE_LIGHTNING';
+
+CREATE TABLE "WalletCoreLightning" (
+ "id" SERIAL NOT NULL,
+ "walletId" INTEGER NOT NULL,
+ "created_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
+ "updated_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
+ "socket" TEXT NOT NULL,
+ "rune" TEXT NOT NULL,
+
+ CONSTRAINT "WalletCoreLightning_pkey" PRIMARY KEY ("id")
+);
+
+CREATE UNIQUE INDEX "WalletCoreLightning_walletId_key" ON "WalletCoreLightning"("walletId");
+
+ALTER TABLE "WalletCoreLightning" ADD CONSTRAINT "WalletCoreLightning_walletId_fkey" FOREIGN KEY ("walletId") REFERENCES "Wallet"("id") ON DELETE CASCADE ON UPDATE CASCADE;
+
+CREATE TRIGGER wallet_core_lightning_as_jsonb
+AFTER INSERT OR UPDATE ON "WalletCoreLightning"
+FOR EACH ROW EXECUTE PROCEDURE wallet_wallet_type_as_jsonb();
diff --git a/prisma/schema.prisma b/prisma/schema.prisma
index 22e520bbb..9183fb40c 100644
--- a/prisma/schema.prisma
+++ b/prisma/schema.prisma
@@ -125,6 +125,7 @@ model User {
enum WalletType {
LIGHTNING_ADDRESS
LND
+ CORE_LIGHTNING
}
model Wallet {
@@ -146,6 +147,7 @@ model Wallet {
wallet Json? @db.JsonB
walletLightningAddress WalletLightningAddress?
walletLND WalletLND?
+ walletCoreLightning WalletCoreLightning?
@@index([userId])
}
@@ -170,6 +172,16 @@ model WalletLND {
cert String?
}
+model WalletCoreLightning {
+ id Int @id @default(autoincrement())
+ walletId Int @unique
+ wallet Wallet @relation(fields: [walletId], references: [id], onDelete: Cascade)
+ createdAt DateTime @default(now()) @map("created_at")
+ updatedAt DateTime @default(now()) @updatedAt @map("updated_at")
+ socket String
+ rune String
+}
+
model Mute {
muterId Int
mutedId Int
diff --git a/worker/autowithdraw.js b/worker/autowithdraw.js
index 55010aeff..00ab055ba 100644
--- a/worker/autowithdraw.js
+++ b/worker/autowithdraw.js
@@ -53,6 +53,10 @@ export async function autoWithdraw ({ data: { id }, models, lnd }) {
await autowithdrawLNAddr(
{ amount, maxFee },
{ models, me: user, lnd })
+ } else if (wallet.type === 'CORE_LIGHTNING') {
+ await autowithdrawCoreLightning(
+ { amount, maxFee },
+ { models, me: user })
}
return
@@ -124,3 +128,41 @@ async function autowithdrawLND ({ amount, maxFee }, { me, models, lnd }) {
return await createWithdrawal(null, { invoice: invoice.request, maxFee }, { me, models, lnd, autoWithdraw: true })
}
+
+async function autowithdrawCoreLightning ({ amount, maxFee }, { me, models }) {
+ if (!me) {
+ throw new Error('me not specified')
+ }
+
+ const wallet = await models.wallet.findFirst({
+ where: {
+ userId: me.id,
+ type: 'CORE_LIGHTNING'
+ },
+ include: {
+ walletCoreLightning: true
+ }
+ })
+
+ if (!wallet || !wallet.walletCoreLightning) {
+ throw new Error('no lightning address wallet found')
+ }
+
+ const { walletCoreLightning: { rune, socket } } = wallet
+ const options = {
+ method: 'POST',
+ headers: {
+ 'content-type': 'application/json',
+ Rune: rune
+ },
+ body: JSON.stringify({
+ amount_msat: '20',
+ label: 'Stacker.News AutoWithdrawal',
+ description: 'Autowithdraw to Core Lightning from SN'
+ })
+ }
+
+ const invoice = await fetch(`${socket}/v1/invoice`, options)
+
+ return await createWithdrawal(null, { invoice: invoice.bolt11, maxFee }, { me, models, autoWithdraw: true })
+}