Skip to content

Commit

Permalink
Add phoenixd as send+recv wallet
Browse files Browse the repository at this point in the history
  • Loading branch information
ekzyis committed Aug 23, 2024
1 parent d68a960 commit 1a567aa
Show file tree
Hide file tree
Showing 9 changed files with 187 additions and 2 deletions.
8 changes: 8 additions & 0 deletions fragments/wallet.js
Original file line number Diff line number Diff line change
Expand Up @@ -137,6 +137,10 @@ export const WALLET = gql`
... on WalletNwc {
nwcUrlRecv
}
... on WalletPhoenixd {
url
secondaryPassword
}
}
}
}
Expand Down Expand Up @@ -173,6 +177,10 @@ export const WALLET_BY_TYPE = gql`
... on WalletNwc {
nwcUrlRecv
}
... on WalletPhoenixd {
url
secondaryPassword
}
}
}
}
Expand Down
18 changes: 18 additions & 0 deletions lib/validate.js
Original file line number Diff line number Diff line change
Expand Up @@ -763,6 +763,24 @@ export const lncSchema = object({
.required('required')
})

export const phoenixdSchema = object().shape({
url: string().url().required('required').trim(),
primaryPassword: string().when(['secondaryPassword'], ([secondary], schema) => {
if (!secondary) return schema.required('required if secondary password not set')
return schema.test({
test: primary => secondary !== primary,
message: 'primary password cannot be the same as secondary password'
})
}),
secondaryPassword: string().when(['primaryPassword'], ([primary], schema) => {
if (!primary) return schema.required('required if primary password not set')
return schema.test({
test: secondary => primary !== secondary,
message: 'secondary password cannot be the same as primary password'
})
})
}, ['primaryPassword', 'secondaryPassword'])

export const bioSchema = object({
bio: string().required('required').trim()
})
Expand Down
24 changes: 24 additions & 0 deletions prisma/migrations/20240821014115_phoenixd/migration.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
-- AlterEnum
ALTER TYPE "WalletType" ADD VALUE 'PHOENIXD';

-- CreateTable
CREATE TABLE "WalletPhoenixd" (
"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,
"url" TEXT NOT NULL,
"secondaryPassword" TEXT NOT NULL,

CONSTRAINT "WalletPhoenixd_pkey" PRIMARY KEY ("id")
);

-- CreateIndex
CREATE UNIQUE INDEX "WalletPhoenixd_walletId_key" ON "WalletPhoenixd"("walletId");

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

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

model Wallet {
Expand All @@ -196,6 +197,7 @@ model Wallet {
walletCLN WalletCLN?
walletLNbits WalletLNbits?
walletNWC WalletNWC?
walletPhoenixd WalletPhoenixd?
withdrawals Withdrawl[]
InvoiceForward InvoiceForward[]
Expand Down Expand Up @@ -264,6 +266,16 @@ model WalletNWC {
nwcUrlRecv String
}

model WalletPhoenixd {
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")
url String
secondaryPassword String
}

model Mute {
muterId Int
mutedId Int
Expand Down
3 changes: 2 additions & 1 deletion wallets/client.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,5 +6,6 @@ import * as cln from 'wallets/cln/client'
import * as lnd from 'wallets/lnd/client'
import * as webln from 'wallets/webln/client'
import * as blink from 'wallets/blink/client'
import * as phoenixd from 'wallets/phoenixd/client'

export default [nwc, lnbits, lnc, lnAddr, cln, lnd, webln, blink]
export default [nwc, lnbits, lnc, lnAddr, cln, lnd, webln, blink, phoenixd]
38 changes: 38 additions & 0 deletions wallets/phoenixd/client.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
export * from 'wallets/phoenixd'

export async function testSendPayment (config, { logger }) {
// TODO:
// Not sure which endpoint to call to test primary password
// see https://phoenix.acinq.co/server/api
// Maybe just wait until test payments with HODL invoices?

}

export async function sendPayment (bolt11, { url, primaryPassword }) {
// https://phoenix.acinq.co/server/api#pay-bolt11-invoice
const path = '/payinvoice'

const headers = new Headers()
headers.set('Authorization', 'Basic ' + Buffer.from(':' + primaryPassword).toString('base64'))
headers.set('Content-type', 'application/x-www-form-urlencoded')

const body = new URLSearchParams()
body.append('invoice', bolt11)

const res = await fetch(url + path, {
method: 'POST',
headers,
body
})
if (!res.ok) {
throw new Error(await res.text())
}

const payment = await res.json()
const preimage = payment.paymentPreimage
if (!preimage) {
throw new Error(payment.reason)
}

return payment.paymentPreimage
}
45 changes: 45 additions & 0 deletions wallets/phoenixd/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
import { phoenixdSchema } from '@/lib/validate'

export const name = 'phoenixd'

// configure wallet fields
export const fields = [
{
name: 'url',
label: 'url',
type: 'text'
},
{
name: 'primaryPassword',
label: 'primary password',
type: 'password',
optional: 'for sending',
help: 'You can find the primary password as `http-password` in your phoenixd configuration file as mentioned [here](https://phoenix.acinq.co/server/api#security).',
clientOnly: true,
editable: false
},
{
name: 'secondaryPassword',
label: 'secondary password',
type: 'password',
optional: 'for receiving',
help: 'You can find the secondary password as `http-password-limited-access` in your phoenixd configuration file as mentioned [here](https://phoenix.acinq.co/server/api#security).',
serverOnly: true,
editable: false
}
]

// configure wallet card
export const card = {
title: 'phoenixd',
subtitle: 'use [phoenixd](https://phoenix.acinq.co/server) for payments',
badges: ['send & receive']
}

// phoenixd::TODO
// set validation schema
export const fieldValidation = phoenixdSchema

export const walletType = 'PHOENIXD'

export const walletField = 'walletPhoenixd'
38 changes: 38 additions & 0 deletions wallets/phoenixd/server.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
import { msatsToSats } from '@/lib/format'

export * from 'wallets/phoenixd'

export async function testCreateInvoice ({ url, secondaryPassword }) {
return await createInvoice(
{ msats: 1000, description: 'SN test invoice', expiry: 1 },
{ url, secondaryPassword })
}

export async function createInvoice (
{ msats, description, descriptionHash, expiry },
{ url, secondaryPassword }
) {
// https://phoenix.acinq.co/server/api#create-bolt11-invoice
const path = '/createinvoice'

const headers = new Headers()
headers.set('Authorization', 'Basic ' + Buffer.from(':' + secondaryPassword).toString('base64'))
headers.set('Content-type', 'application/x-www-form-urlencoded')

const body = new URLSearchParams()
body.append('description', description)
body.append('amountSat', msatsToSats(msats))

const res = await fetch(url + path, {
method: 'POST',
headers,
body
})
if (!res.ok) {
const error = await res.text()
throw new Error(error)
}

const payment = await res.json()
return payment.serialized
}
3 changes: 2 additions & 1 deletion wallets/server.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,13 @@ import * as cln from 'wallets/cln/server'
import * as lnAddr from 'wallets/lightning-address/server'
import * as lnbits from 'wallets/lnbits/server'
import * as nwc from 'wallets/nwc/server'
import * as phoenixd from 'wallets/phoenixd/server'
import { addWalletLog } from '@/api/resolvers/wallet'
import walletDefs from 'wallets/server'
import { parsePaymentRequest } from 'ln-service'
import { toPositiveNumber } from '@/lib/validate'
import { PAID_ACTION_TERMINAL_STATES } from '@/lib/constants'
export default [lnd, cln, lnAddr, lnbits, nwc]
export default [lnd, cln, lnAddr, lnbits, nwc, phoenixd]

const MAX_PENDING_INVOICES_PER_WALLET = 25

Expand Down

0 comments on commit 1a567aa

Please sign in to comment.