Phoenixd send+recv (#1322)
* Add genwallet script * Add phoenixd as send+recv wallet * phoenixd passwords are 64 hex chars
This commit is contained in:
parent
5cfefc1ca8
commit
cc003a9a3e
@ -137,6 +137,10 @@ export const WALLET = gql`
|
||||
... on WalletNwc {
|
||||
nwcUrlRecv
|
||||
}
|
||||
... on WalletPhoenixd {
|
||||
url
|
||||
secondaryPassword
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -173,6 +177,10 @@ export const WALLET_BY_TYPE = gql`
|
||||
... on WalletNwc {
|
||||
nwcUrlRecv
|
||||
}
|
||||
... on WalletPhoenixd {
|
||||
url
|
||||
secondaryPassword
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -762,6 +762,26 @@ export const lncSchema = object({
|
||||
.required('required')
|
||||
})
|
||||
|
||||
export const phoenixdSchema = object().shape({
|
||||
url: string().url().required('required').trim(),
|
||||
primaryPassword: string().length(64).hex()
|
||||
.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().length(64).hex()
|
||||
.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()
|
||||
})
|
||||
|
24
prisma/migrations/20240821014115_phoenixd/migration.sql
Normal file
24
prisma/migrations/20240821014115_phoenixd/migration.sql
Normal file
@ -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();
|
@ -171,6 +171,7 @@ enum WalletType {
|
||||
CLN
|
||||
LNBITS
|
||||
NWC
|
||||
PHOENIXD
|
||||
}
|
||||
|
||||
model Wallet {
|
||||
@ -196,6 +197,7 @@ model Wallet {
|
||||
walletCLN WalletCLN?
|
||||
walletLNbits WalletLNbits?
|
||||
walletNWC WalletNWC?
|
||||
walletPhoenixd WalletPhoenixd?
|
||||
withdrawals Withdrawl[]
|
||||
InvoiceForward InvoiceForward[]
|
||||
|
||||
@ -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
|
||||
|
184
scripts/genwallet.sh
Normal file
184
scripts/genwallet.sh
Normal file
@ -0,0 +1,184 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
cat <<EOF
|
||||
.__ .__ __
|
||||
____ ____ ______ _ _______ | | | | _____/ |_
|
||||
/ ___\_/ __ \ / \ \/ \/ /\__ \ | | | | _/ __ \ __\\
|
||||
/ /_/ > ___/| | \ / / __ \| |_| |_\ ___/| |
|
||||
\___ / \___ >___| /\/\_/ (____ /____/____/\___ >__|
|
||||
/_____/ \/ \/ \/ \/
|
||||
|
||||
EOF
|
||||
|
||||
error () {
|
||||
echo -n "error: $1"
|
||||
exit 1
|
||||
}
|
||||
|
||||
wallet=$1
|
||||
[ -z $wallet ] && read -p "Enter wallet name: " wallet
|
||||
[ -z $wallet ] && error "name required"
|
||||
|
||||
# default is wallet in UPPERCASE
|
||||
walletType="${wallet^^}"
|
||||
read -p "Enter walletType (default $walletType): " _walletType
|
||||
if [ ! -z $_walletType ]; then
|
||||
walletType=$_walletType
|
||||
fi
|
||||
|
||||
# default is wallet capitalized with "wallet" prefix
|
||||
walletField="wallet${wallet^}"
|
||||
read -p "Enter walletField (default $walletField): " _walletField
|
||||
if [ ! -z $_walletField ]; then
|
||||
walletField=$_walletField
|
||||
fi
|
||||
|
||||
# exit on first failed command
|
||||
set -e
|
||||
|
||||
todo() {
|
||||
echo "// $wallet::TODO"
|
||||
}
|
||||
|
||||
# create folder and index.js
|
||||
mkdir -p wallets/$wallet
|
||||
cat > wallets/$wallet/index.js <<EOF
|
||||
$(todo)
|
||||
// create validation schema for wallet and import here
|
||||
// import { ${wallet}Schema } from '@/lib/validate'
|
||||
|
||||
export const name = '$wallet'
|
||||
|
||||
$(todo)
|
||||
// configure wallet fields
|
||||
export const fields = []
|
||||
|
||||
$(todo)
|
||||
// configure wallet card
|
||||
export const card = {
|
||||
title: '$wallet',
|
||||
subtitle: '',
|
||||
badges: []
|
||||
}
|
||||
|
||||
$(todo)
|
||||
// set validation schema
|
||||
export const fieldValidation = null // ${wallet}Schema
|
||||
|
||||
export const walletType = '$walletType'
|
||||
|
||||
export const walletField = '$walletField'
|
||||
|
||||
EOF
|
||||
|
||||
# create client.js
|
||||
cat > wallets/$wallet/client.js <<EOF
|
||||
export * from 'wallets/$wallet'
|
||||
|
||||
export async function testSendPayment (config, { logger }) {
|
||||
$(todo)
|
||||
}
|
||||
|
||||
export async function sendPayment (bolt11, config) {
|
||||
$(todo)
|
||||
}
|
||||
|
||||
EOF
|
||||
|
||||
# create server.js
|
||||
cat > wallets/$wallet/server.js <<EOF
|
||||
export * from 'wallets/$wallet'
|
||||
|
||||
export async function testCreateInvoice (config) {
|
||||
$(todo)
|
||||
}
|
||||
|
||||
export async function createInvoice (
|
||||
{ msats, description, descriptionHash, expiry },
|
||||
config
|
||||
) {
|
||||
$(todo)
|
||||
}
|
||||
|
||||
EOF
|
||||
|
||||
# add TODOs where manual update is needed
|
||||
fragments=fragments/wallet.js
|
||||
i=0
|
||||
grep -n "// XXX \[WALLET\]" $fragments | while read -r match;
|
||||
do
|
||||
lineno=$(echo $match | cut -d':' -f1)
|
||||
sed -i "$((lineno+i))i $(todo)" $fragments
|
||||
i=$((i+1))
|
||||
done
|
||||
|
||||
client=wallets/client.js
|
||||
lineno=$(grep -n "export default" $client | cut -d':' -f1)
|
||||
sed -i "${lineno}i $(todo)" $client
|
||||
|
||||
server=wallets/server.js
|
||||
lineno=$(grep -n "export default" $server | cut -d':' -f1)
|
||||
sed -i "${lineno}i $(todo)" $server
|
||||
|
||||
# need to disable exit on failure since we run grep to check its exit code
|
||||
set +e
|
||||
|
||||
# check if prisma/schema.prisma needs patch
|
||||
schema=prisma/schema.prisma
|
||||
grep --quiet "$walletField" $schema
|
||||
if [ $? -eq 1 ]; then
|
||||
tablename=${walletField^}
|
||||
# find line to insert walletField in wallet model
|
||||
lineno=$(grep -n "model Wallet {" $schema | cut -d':' -f1)
|
||||
offset=$(tail -n +$lineno $schema | grep -nm 1 "}" | cut -d':' -f1)
|
||||
offset=$(tail -n +$lineno $schema | head -n $offset | grep -nE "wallet[[:alpha:]]+\s+ Wallet[[:alpha:]]" | cut -d':' -f1 | tail -n1)
|
||||
sed -i "$((lineno+offset))i\ \ $walletField $tablename?" $schema
|
||||
|
||||
# find line to insert model for wallet
|
||||
lineno=$(grep -nE "model Wallet[[:alpha:]]+ {" $schema | cut -d':' -f1 | tail -n1)
|
||||
offset=$(tail -n +$((lineno+1)) $schema | grep -nm 1 "{" | cut -d':' -f1)
|
||||
i=$((lineno+offset))
|
||||
sed -i "${i}i $(todo)" $schema
|
||||
sed -i "$((i+1))i model Wallet${wallet^} {\n" $schema
|
||||
sed -i "$((i+2))i\ \ id Int @id @default(autoincrement())" $schema
|
||||
sed -i "$((i+3))i\ \ walletId Int @unique" $schema
|
||||
sed -i "$((i+4))i\ \ wallet Wallet @relation(fields: [walletId], references: [id], onDelete: Cascade)" $schema
|
||||
sed -i "$((i+5))i\ \ createdAt DateTime @default(now()) @map(\"created_at\")" $schema
|
||||
sed -i "$((i+6))i\ \ updatedAt DateTime @default(now()) @updatedAt @map(\"updated_at\")" $schema
|
||||
sed -i "$((i+7))i }" $schema
|
||||
|
||||
# find line to insert wallet type
|
||||
lineno=$(grep -nE "enum WalletType {" $schema | cut -d':' -f1)
|
||||
offset=$(tail -n +$lineno $schema | grep -nm 1 "}" | cut -d':' -f1)
|
||||
i=$((lineno+offset-1))
|
||||
sed -i "${i} i\ \ ${walletType}" $schema
|
||||
|
||||
# create migration file with TODOs
|
||||
migrationDir="prisma/migrations/$(date +%Y%m%d%H%M%S_$wallet)"
|
||||
mkdir -p $migrationDir
|
||||
cat > $migrationDir/migration.sql <<EOF
|
||||
-- AlterEnum
|
||||
ALTER TYPE "WalletType" ADD VALUE '${walletType}';
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE "$tablename" (
|
||||
"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,
|
||||
-- $wallet::TODO
|
||||
|
||||
CONSTRAINT "${tablename}_pkey" PRIMARY KEY ("int")
|
||||
);
|
||||
|
||||
-- CreateIndex
|
||||
CREATE UNIQUE INDEX "${tablename}_walletId_key" ON "$tablename"("walletId");
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "$tablename" ADD CONSTRAINT "${tablename}_walletId_fkey" FOREIGN KEY ("walletId") REFERENCES "Wallet"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
|
||||
CREATE TRIGGER wallet_${wallet}_as_jsonb
|
||||
AFTER INSERT OR UPDATE ON "$tablename"
|
||||
FOR EACH ROW EXECUTE PROCEDURE wallet_wallet_type_as_jsonb();
|
||||
EOF
|
||||
fi
|
@ -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]
|
||||
|
39
wallets/phoenixd/client.js
Normal file
39
wallets/phoenixd/client.js
Normal file
@ -0,0 +1,39 @@
|
||||
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) {
|
||||
const error = await res.text()
|
||||
throw new Error(error)
|
||||
}
|
||||
|
||||
const payment = await res.json()
|
||||
const preimage = payment.paymentPreimage
|
||||
if (!preimage) {
|
||||
throw new Error(payment.reason)
|
||||
}
|
||||
|
||||
return payment.paymentPreimage
|
||||
}
|
45
wallets/phoenixd/index.js
Normal file
45
wallets/phoenixd/index.js
Normal file
@ -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
wallets/phoenixd/server.js
Normal file
38
wallets/phoenixd/server.js
Normal file
@ -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,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
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user