Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

LNC Receive #1342

Draft
wants to merge 9 commits into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions fragments/wallet.js
Original file line number Diff line number Diff line change
Expand Up @@ -141,6 +141,12 @@ export const WALLET = gql`
url
secondaryPassword
}
... on WalletLnc {
pairingPhraseRecv
localKeyRecv
remoteKeyRecv
serverHostRecv
}
}
}
}
Expand Down Expand Up @@ -181,6 +187,12 @@ export const WALLET_BY_TYPE = gql`
url
secondaryPassword
}
... on WalletLnc {
pairingPhraseRecv
localKeyRecv
remoteKeyRecv
serverHostRecv
}
}
}
}
Expand Down
43 changes: 37 additions & 6 deletions lib/validate.js
Original file line number Diff line number Diff line change
Expand Up @@ -740,10 +740,15 @@ export const blinkSchema = object({
.oneOf(['USD', 'BTC'], 'must be BTC or USD')
})

export const lncSchema = object({
pairingPhrase: string()
.test(async (value, context) => {
export const lncSchema = object().shape({
pairingPhrase: string().when(['pairingPhraseRecv'], ([pairingPhraseRecv], schema) => {
if (!pairingPhraseRecv) return schema.required('required if connection for receiving not set')
return schema.test({
test: pairingPhrase => pairingPhraseRecv !== pairingPhrase,
message: 'connection for sending cannot be the same as for receiving'
}).test(async (value, context) => {
const words = value ? value.trim().split(/[\s]+/) : []
if (!words.length) return true
for (const w of words) {
try {
await string().oneOf(bip39Words).validate(w)
Expand All @@ -758,9 +763,35 @@ export const lncSchema = object({
return context.createError({ message: 'max 10 words' })
}
return true
})
.required('required')
})
}
)
}),
pairingPhraseRecv: string().when(['pairingPhrase'], ([pairingPhrase], schema) => {
if (!pairingPhrase) return schema.required('required if connection for receiving not set')
return schema.test({
test: pairingPhraseRecv => pairingPhraseRecv !== pairingPhrase,
message: 'connection for sending cannot be the same as for receiving'
}).test(async (value, context) => {
const words = value ? value.trim().split(/[\s]+/) : []
if (!words.length) return true
for (const w of words) {
try {
await string().oneOf(bip39Words).validate(w)
} catch {
return context.createError({ message: `'${w}' is not a valid pairing phrase word` })
}
}
if (words.length < 2) {
return context.createError({ message: 'needs at least two words' })
}
if (words.length > 10) {
return context.createError({ message: 'max 10 words' })
}
return true
}
)
})
}, ['pairingPhrase', 'pairingPhraseRecv'])

export const phoenixdSchema = object().shape({
url: string().url().required('required').trim(),
Expand Down
62 changes: 31 additions & 31 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,7 @@
"nprogress": "^0.2.0",
"opentimestamps": "^0.4.9",
"page-metadata-parser": "^1.1.4",
"pg": "^8.12.0",
"pg-boss": "^9.0.3",
"piexifjs": "^1.0.6",
"prisma": "^5.17.0",
Expand Down
25 changes: 25 additions & 0 deletions prisma/migrations/20240827182401_lnc_recv/migration.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
-- AlterEnum
ALTER TYPE "WalletType" ADD VALUE 'LNC';

-- CreateTable
CREATE TABLE "WalletLNC" (
"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,
"pairingPhraseRecv" TEXT NOT NULL,
"localKeyRecv" TEXT,
"remoteKeyRecv" TEXT,
"serverHostRecv" TEXT,
CONSTRAINT "WalletLNC_pkey" PRIMARY KEY ("id")
);

-- CreateIndex
CREATE UNIQUE INDEX "WalletLNC_walletId_key" ON "WalletLNC"("walletId");

-- AddForeignKey
ALTER TABLE "WalletLNC" ADD CONSTRAINT "WalletLNC_walletId_fkey" FOREIGN KEY ("walletId") REFERENCES "Wallet"("id") ON DELETE CASCADE ON UPDATE CASCADE;

CREATE TRIGGER wallet_lnc_as_jsonb
AFTER INSERT OR UPDATE ON "WalletLNC"
FOR EACH ROW EXECUTE PROCEDURE wallet_wallet_type_as_jsonb();
14 changes: 14 additions & 0 deletions prisma/schema.prisma
Original file line number Diff line number Diff line change
Expand Up @@ -173,6 +173,7 @@ enum WalletType {
LNBITS
NWC
PHOENIXD
LNC
}

model Wallet {
Expand All @@ -199,6 +200,7 @@ model Wallet {
walletLNbits WalletLNbits?
walletNWC WalletNWC?
walletPhoenixd WalletPhoenixd?
walletLNC WalletLNC?
withdrawals Withdrawl[]
InvoiceForward InvoiceForward[]

Expand Down Expand Up @@ -267,6 +269,18 @@ model WalletNWC {
nwcUrlRecv String
}

model WalletLNC {
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")
pairingPhraseRecv String
localKeyRecv String?
remoteKeyRecv String?
serverHostRecv String?
}

model WalletPhoenixd {
id Int @id @default(autoincrement())
walletId Int @unique
Expand Down
45 changes: 17 additions & 28 deletions wallets/lnc/client.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import { InvoiceCanceledError, InvoiceExpiredError } from '@/components/payment'
import { bolt11Tags } from '@/lib/bolt11'
import { Mutex } from 'async-mutex'
import { computePerms } from 'wallets/lnc'

export * from 'wallets/lnc'

async function disconnect (lnc, logger) {
Expand All @@ -22,9 +24,8 @@ async function disconnect (lnc, logger) {
}
clearInterval(interval)
resolve()
})
}, 50)
logger.info('disconnected')
}, 50)
})
} catch (err) {
logger.error('failed to disconnect from lnc', err)
}
Expand All @@ -41,7 +42,7 @@ export async function testSendPayment (credentials, { logger }) {
logger.ok('connected')

logger.info('validating permissions ...')
await validateNarrowPerms(lnc)
checkPerms(lnc, computePerms({ canSend: true }))
logger.ok('permissions ok')

return lnc.credentials.credentials
Expand Down Expand Up @@ -84,36 +85,24 @@ export async function sendPayment (bolt11, credentials, { logger }) {
}

async function getLNC (credentials = {}) {
const serverHost = 'mailbox.terminal.lightning.today:443'
// XXX we MUST reuse the same instance of LNC because it references a global Go object
// that holds closures to the first LNC instance it's created with
if (window.lnc) {
window.lnc.credentials.credentials = {
...window.lnc.credentials.credentials,
...credentials,
serverHost
}
return window.lnc
}
const { default: { default: LNC } } = await import('@lightninglabs/lnc-web')
window.lnc = new LNC({
credentialStore: new LncCredentialStore({
...credentials,
serverHost
})
return new LNC({
credentialStore: new LncCredentialStore({ ...credentials, serverHost: 'mailbox.terminal.lightning.today:443' })
})
return window.lnc
}

function validateNarrowPerms (lnc) {
if (!lnc.hasPerms('lnrpc.Lightning.SendPaymentSync')) {
throw new Error('missing permission: lnrpc.Lightning.SendPaymentSync')
function checkPerms (lnc, { expectedPerms, unexpectedPerms }) {
for (const perm of expectedPerms) {
if (!lnc.hasPerms(perm)) {
throw new Error('missing permission: ' + perm)
}
}
if (lnc.hasPerms('lnrpc.Lightning.SendCoins')) {
throw new Error('too broad permission: lnrpc.Wallet.SendCoins')

for (const perm of unexpectedPerms) {
if (lnc.hasPerms(perm)) {
throw new Error('unexpected permission: ' + perm)
}
}
// TODO: need to check for more narrow permissions
// blocked by https://github.com/lightninglabs/lnc-web/issues/112
}

// default credential store can go fuck itself
Expand Down
Loading
Loading