Compare commits
7 Commits
ffc156df2b
...
3608d133d7
Author | SHA1 | Date | |
---|---|---|---|
|
3608d133d7 | ||
|
506bb364d1 | ||
|
9932a782b2 | ||
|
11ddbe3983 | ||
|
2d139bed85 | ||
|
3d8ae4a7a3 | ||
|
d2d04ce141 |
@ -13,7 +13,7 @@ import assertApiKeyNotPermitted from './apiKey'
|
|||||||
import { bolt11Tags } from '@/lib/bolt11'
|
import { bolt11Tags } from '@/lib/bolt11'
|
||||||
import { finalizeHodlInvoice } from 'worker/wallet'
|
import { finalizeHodlInvoice } from 'worker/wallet'
|
||||||
import walletDefs from 'wallets/server'
|
import walletDefs from 'wallets/server'
|
||||||
import { generateResolverName } from '@/lib/wallet'
|
import { generateResolverName, walletTypeToResolveType } from '@/lib/wallet'
|
||||||
import { lnAddrOptions } from '@/lib/lnurl'
|
import { lnAddrOptions } from '@/lib/lnurl'
|
||||||
|
|
||||||
function injectResolvers (resolvers) {
|
function injectResolvers (resolvers) {
|
||||||
@ -349,11 +349,17 @@ const resolvers = {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
WalletDetails: {
|
Wallet: {
|
||||||
__resolveType (wallet) {
|
wallet: async (wallet) => {
|
||||||
return wallet.address ? 'WalletLNAddr' : wallet.macaroon ? 'WalletLND' : wallet.rune ? 'WalletCLN' : 'WalletLNbits'
|
return {
|
||||||
|
...wallet.wallet,
|
||||||
|
__resolveType: walletTypeToResolveType(wallet.type)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
WalletDetails: {
|
||||||
|
__resolveType: wallet => wallet.__resolveType
|
||||||
|
},
|
||||||
Mutation: {
|
Mutation: {
|
||||||
createInvoice: async (parent, { amount, hodlInvoice = false, expireSecs = 3600 }, { me, models, lnd, headers }) => {
|
createInvoice: async (parent, { amount, hodlInvoice = false, expireSecs = 3600 }, { me, models, lnd, headers }) => {
|
||||||
await ssValidate(amountSchema, { amount })
|
await ssValidate(amountSchema, { amount })
|
||||||
|
@ -1,32 +1,59 @@
|
|||||||
import { gql } from 'graphql-tag'
|
import { gql } from 'graphql-tag'
|
||||||
import { generateResolverName } from '@/lib/wallet'
|
import { fieldToGqlArg, generateResolverName, generateTypeDefName } from '@/lib/wallet'
|
||||||
|
|
||||||
import walletDefs from 'wallets/server'
|
import walletDefs from 'wallets/server'
|
||||||
import { isServerField } from 'wallets'
|
import { isServerField } from 'wallets'
|
||||||
|
|
||||||
function injectTypeDefs (typeDefs) {
|
function injectTypeDefs (typeDefs) {
|
||||||
console.group('injected GraphQL type defs:')
|
const injected = [rawTypeDefs(), mutationTypeDefs()]
|
||||||
const injected = walletDefs.map(
|
return `${typeDefs}\n\n${injected.join('\n\n')}\n`
|
||||||
(w) => {
|
}
|
||||||
let args = 'id: ID, '
|
|
||||||
args += w.fields
|
function mutationTypeDefs () {
|
||||||
.filter(isServerField)
|
console.group('injected GraphQL mutations:')
|
||||||
.map(f => {
|
|
||||||
let arg = `${f.name}: String`
|
const typeDefs = walletDefs.map((w) => {
|
||||||
if (!f.optional) {
|
let args = 'id: ID, '
|
||||||
arg += '!'
|
args += w.fields
|
||||||
}
|
.filter(isServerField)
|
||||||
return arg
|
.map(fieldToGqlArg).join(', ')
|
||||||
}).join(', ')
|
args += ', settings: AutowithdrawSettings!'
|
||||||
args += ', settings: AutowithdrawSettings!'
|
const resolverName = generateResolverName(w.walletField)
|
||||||
const resolverName = generateResolverName(w.walletField)
|
const typeDef = `${resolverName}(${args}): Boolean`
|
||||||
const typeDef = `${resolverName}(${args}): Boolean`
|
console.log(typeDef)
|
||||||
console.log(typeDef)
|
return typeDef
|
||||||
return typeDef
|
})
|
||||||
})
|
|
||||||
console.groupEnd()
|
console.groupEnd()
|
||||||
|
|
||||||
return `${typeDefs}\n\nextend type Mutation {\n${injected.join('\n')}\n}`
|
return `extend type Mutation {\n${typeDefs.join('\n')}\n}`
|
||||||
|
}
|
||||||
|
|
||||||
|
function rawTypeDefs () {
|
||||||
|
console.group('injected GraphQL type defs:')
|
||||||
|
|
||||||
|
const typeDefs = walletDefs.map((w) => {
|
||||||
|
const args = w.fields
|
||||||
|
.filter(isServerField)
|
||||||
|
.map(fieldToGqlArg)
|
||||||
|
.map(s => ' ' + s)
|
||||||
|
.join('\n')
|
||||||
|
const typeDefName = generateTypeDefName(w.walletField)
|
||||||
|
const typeDef = `type ${typeDefName} {\n${args}\n}`
|
||||||
|
console.log(typeDef)
|
||||||
|
return typeDef
|
||||||
|
})
|
||||||
|
|
||||||
|
let union = 'union WalletDetails = '
|
||||||
|
union += walletDefs.map((w) => {
|
||||||
|
const typeDefName = generateTypeDefName(w.walletField)
|
||||||
|
return typeDefName
|
||||||
|
}).join(' | ')
|
||||||
|
console.log(union)
|
||||||
|
|
||||||
|
console.groupEnd()
|
||||||
|
|
||||||
|
return typeDefs.join('\n\n') + union
|
||||||
}
|
}
|
||||||
|
|
||||||
const typeDefs = `
|
const typeDefs = `
|
||||||
@ -61,29 +88,6 @@ const typeDefs = `
|
|||||||
wallet: WalletDetails!
|
wallet: WalletDetails!
|
||||||
}
|
}
|
||||||
|
|
||||||
type WalletLNAddr {
|
|
||||||
address: String!
|
|
||||||
}
|
|
||||||
|
|
||||||
type WalletLND {
|
|
||||||
socket: String!
|
|
||||||
macaroon: String!
|
|
||||||
cert: String
|
|
||||||
}
|
|
||||||
|
|
||||||
type WalletCLN {
|
|
||||||
socket: String!
|
|
||||||
rune: String!
|
|
||||||
cert: String
|
|
||||||
}
|
|
||||||
|
|
||||||
type WalletLNbits {
|
|
||||||
url: String!
|
|
||||||
invoiceKey: String!
|
|
||||||
}
|
|
||||||
|
|
||||||
union WalletDetails = WalletLNAddr | WalletLND | WalletCLN | WalletLNbits
|
|
||||||
|
|
||||||
input AutowithdrawSettings {
|
input AutowithdrawSettings {
|
||||||
autoWithdrawThreshold: Int!
|
autoWithdrawThreshold: Int!
|
||||||
autoWithdrawMaxFeePercent: Float!
|
autoWithdrawMaxFeePercent: Float!
|
||||||
|
@ -120,3 +120,4 @@ OneOneSeven117,issue,#1272,#1268,easy,,,,10k,OneOneSeven@stacker.news,2024-07-31
|
|||||||
aniskhalfallah,pr,#1264,#1226,good-first-issue,,,,20k,aniskhalfallah@stacker.news,2024-07-31
|
aniskhalfallah,pr,#1264,#1226,good-first-issue,,,,20k,aniskhalfallah@stacker.news,2024-07-31
|
||||||
Gudnessuche,issue,#1264,#1226,good-first-issue,,,,2k,everythingsatoshi@getalby.com,2024-08-10
|
Gudnessuche,issue,#1264,#1226,good-first-issue,,,,2k,everythingsatoshi@getalby.com,2024-08-10
|
||||||
aniskhalfallah,pr,#1289,,easy,,,,100k,aniskhalfallah@blink.sv,2024-08-12
|
aniskhalfallah,pr,#1289,,easy,,,,100k,aniskhalfallah@blink.sv,2024-08-12
|
||||||
|
riccardobl,pr,#1293,#1142,medium,high,,,500k,rblb@getalby.com,2024-08-18
|
||||||
|
|
@ -10,3 +10,4 @@ mz
|
|||||||
btcbagehot
|
btcbagehot
|
||||||
felipe
|
felipe
|
||||||
benalleng
|
benalleng
|
||||||
|
rblb
|
@ -107,6 +107,7 @@ mutation removeWallet($id: ID!) {
|
|||||||
}
|
}
|
||||||
`
|
`
|
||||||
|
|
||||||
|
// XXX [WALLET] this needs to be updated if another server wallet is added
|
||||||
export const WALLET = gql`
|
export const WALLET = gql`
|
||||||
query Wallet($id: ID!) {
|
query Wallet($id: ID!) {
|
||||||
wallet(id: $id) {
|
wallet(id: $id) {
|
||||||
@ -116,7 +117,7 @@ export const WALLET = gql`
|
|||||||
type
|
type
|
||||||
wallet {
|
wallet {
|
||||||
__typename
|
__typename
|
||||||
... on WalletLNAddr {
|
... on WalletLightningAddress {
|
||||||
address
|
address
|
||||||
}
|
}
|
||||||
... on WalletLND {
|
... on WalletLND {
|
||||||
@ -138,6 +139,7 @@ export const WALLET = gql`
|
|||||||
}
|
}
|
||||||
`
|
`
|
||||||
|
|
||||||
|
// XXX [WALLET] this needs to be updated if another server wallet is added
|
||||||
export const WALLET_BY_TYPE = gql`
|
export const WALLET_BY_TYPE = gql`
|
||||||
query WalletByType($type: String!) {
|
query WalletByType($type: String!) {
|
||||||
walletByType(type: $type) {
|
walletByType(type: $type) {
|
||||||
@ -148,7 +150,7 @@ export const WALLET_BY_TYPE = gql`
|
|||||||
type
|
type
|
||||||
wallet {
|
wallet {
|
||||||
__typename
|
__typename
|
||||||
... on WalletLNAddr {
|
... on WalletLightningAddress {
|
||||||
address
|
address
|
||||||
}
|
}
|
||||||
... on WalletLND {
|
... on WalletLND {
|
||||||
|
@ -712,6 +712,15 @@ export const nwcSchema = object({
|
|||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
export const blinkSchema = object({
|
||||||
|
apiKey: string()
|
||||||
|
.required('required')
|
||||||
|
.matches(/^blink_[A-Za-z0-9]+$/, { message: 'must match pattern blink_A-Za-z0-9' }),
|
||||||
|
currency: string()
|
||||||
|
.transform(value => value ? value.toUpperCase() : 'BTC')
|
||||||
|
.oneOf(['USD', 'BTC'], 'must be BTC or USD')
|
||||||
|
})
|
||||||
|
|
||||||
export const lncSchema = object({
|
export const lncSchema = object({
|
||||||
pairingPhrase: array()
|
pairingPhrase: array()
|
||||||
.transform(function (value, originalValue) {
|
.transform(function (value, originalValue) {
|
||||||
|
@ -1,4 +1,22 @@
|
|||||||
|
export function fieldToGqlArg (field) {
|
||||||
|
let arg = `${field.name}: String`
|
||||||
|
if (!field.optional) {
|
||||||
|
arg += '!'
|
||||||
|
}
|
||||||
|
return arg
|
||||||
|
}
|
||||||
|
|
||||||
export function generateResolverName (walletField) {
|
export function generateResolverName (walletField) {
|
||||||
const capitalized = walletField[0].toUpperCase() + walletField.slice(1)
|
const capitalized = walletField[0].toUpperCase() + walletField.slice(1)
|
||||||
return `upsert${capitalized}`
|
return `upsert${capitalized}`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function generateTypeDefName (walletField) {
|
||||||
|
return walletField[0].toUpperCase() + walletField.slice(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
export function walletTypeToResolveType (walletType) {
|
||||||
|
// wallet type is in UPPER_CASE but __resolveType requires PascalCase
|
||||||
|
const PascalCase = walletType.split('_').map(s => s[0].toUpperCase() + s.slice(1).toLowerCase()).join('')
|
||||||
|
return `Wallet${PascalCase}`
|
||||||
|
}
|
||||||
|
@ -102,7 +102,7 @@ export default function WalletSettings () {
|
|||||||
|
|
||||||
function WalletFields ({ wallet: { config, fields, isConfigured } }) {
|
function WalletFields ({ wallet: { config, fields, isConfigured } }) {
|
||||||
return fields
|
return fields
|
||||||
.map(({ name, label, type, help, optional, editable, clientOnly, serverOnly, ...props }, i) => {
|
.map(({ name, label = '', type, help, optional, editable, clientOnly, serverOnly, ...props }, i) => {
|
||||||
const rawProps = {
|
const rawProps = {
|
||||||
...props,
|
...props,
|
||||||
name,
|
name,
|
||||||
@ -115,7 +115,7 @@ function WalletFields ({ wallet: { config, fields, isConfigured } }) {
|
|||||||
{label}
|
{label}
|
||||||
{/* help can be a string or object to customize the label */}
|
{/* help can be a string or object to customize the label */}
|
||||||
{help && (
|
{help && (
|
||||||
<Info label={help.label || 'help'}>
|
<Info label={help.label}>
|
||||||
<Text>{help.text || help}</Text>
|
<Text>{help.text || help}</Text>
|
||||||
</Info>
|
</Info>
|
||||||
)}
|
)}
|
||||||
|
188
wallets/blink/client.js
Normal file
188
wallets/blink/client.js
Normal file
@ -0,0 +1,188 @@
|
|||||||
|
import { galoyBlinkUrl } from 'wallets/blink'
|
||||||
|
export * from 'wallets/blink'
|
||||||
|
|
||||||
|
export async function testConnectClient ({ apiKey, currency }, { logger }) {
|
||||||
|
currency = currency ? currency.toUpperCase() : 'BTC'
|
||||||
|
logger.info('trying to fetch ' + currency + ' wallet')
|
||||||
|
await getWallet(apiKey, currency)
|
||||||
|
logger.ok(currency + ' wallet found')
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function sendPayment (bolt11, { apiKey, currency }) {
|
||||||
|
const wallet = await getWallet(apiKey, currency)
|
||||||
|
const preImage = await payInvoice(apiKey, wallet, bolt11)
|
||||||
|
return { preImage }
|
||||||
|
}
|
||||||
|
|
||||||
|
async function payInvoice (authToken, wallet, invoice) {
|
||||||
|
const walletId = wallet.id
|
||||||
|
const out = await request(authToken, `
|
||||||
|
mutation LnInvoicePaymentSend($input: LnInvoicePaymentInput!) {
|
||||||
|
lnInvoicePaymentSend(input: $input) {
|
||||||
|
status
|
||||||
|
errors {
|
||||||
|
message
|
||||||
|
path
|
||||||
|
code
|
||||||
|
}
|
||||||
|
transaction {
|
||||||
|
settlementVia {
|
||||||
|
... on SettlementViaIntraLedger {
|
||||||
|
preImage
|
||||||
|
}
|
||||||
|
... on SettlementViaLn {
|
||||||
|
preImage
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`,
|
||||||
|
{
|
||||||
|
input: {
|
||||||
|
paymentRequest: invoice,
|
||||||
|
walletId
|
||||||
|
}
|
||||||
|
})
|
||||||
|
const status = out.data.lnInvoicePaymentSend.status
|
||||||
|
const errors = out.data.lnInvoicePaymentSend.errors
|
||||||
|
if (errors && errors.length > 0) {
|
||||||
|
throw new Error('failed to pay invoice ' + errors.map(e => e.code + ' ' + e.message).join(', '))
|
||||||
|
}
|
||||||
|
|
||||||
|
// payment was settled immediately
|
||||||
|
if (status === 'SUCCESS') {
|
||||||
|
const preimage = out.data.lnInvoicePaymentSend.transaction.settlementVia.preImage
|
||||||
|
if (!preimage) throw new Error('no preimage')
|
||||||
|
return preimage
|
||||||
|
}
|
||||||
|
|
||||||
|
// payment failed immediately
|
||||||
|
if (status === 'FAILED') {
|
||||||
|
throw new Error('failed to pay invoice')
|
||||||
|
}
|
||||||
|
|
||||||
|
// payment couldn't be settled (or fail) immediately, so we wait for a result
|
||||||
|
if (status === 'PENDING') {
|
||||||
|
while (true) {
|
||||||
|
// at some point it should either be settled or fail on the backend, so the loop will exit
|
||||||
|
await new Promise(resolve => setTimeout(resolve, 100))
|
||||||
|
|
||||||
|
const txInfo = await getTxInfo(authToken, wallet, invoice)
|
||||||
|
// settled
|
||||||
|
if (txInfo.status === 'SUCCESS') {
|
||||||
|
if (!txInfo.preImage) throw new Error('no preimage')
|
||||||
|
return txInfo.preImage
|
||||||
|
}
|
||||||
|
// failed
|
||||||
|
if (txInfo.status === 'FAILED') {
|
||||||
|
throw new Error(txInfo.error || 'failed to pay invoice')
|
||||||
|
}
|
||||||
|
// still pending
|
||||||
|
// retry later
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// this should never happen
|
||||||
|
throw new Error('unexpected error')
|
||||||
|
}
|
||||||
|
|
||||||
|
async function getTxInfo (authToken, wallet, invoice) {
|
||||||
|
const walletId = wallet.id
|
||||||
|
let out
|
||||||
|
try {
|
||||||
|
out = await request(authToken, `
|
||||||
|
query GetTxInfo($walletId: WalletId!, $paymentRequest: LnPaymentRequest!) {
|
||||||
|
me {
|
||||||
|
defaultAccount {
|
||||||
|
walletById(walletId: $walletId) {
|
||||||
|
transactionsByPaymentRequest(paymentRequest: $paymentRequest) {
|
||||||
|
status
|
||||||
|
direction
|
||||||
|
settlementVia {
|
||||||
|
... on SettlementViaIntraLedger {
|
||||||
|
preImage
|
||||||
|
}
|
||||||
|
... on SettlementViaLn {
|
||||||
|
preImage
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`,
|
||||||
|
{
|
||||||
|
paymentRequest: invoice,
|
||||||
|
walletId
|
||||||
|
})
|
||||||
|
} catch (e) {
|
||||||
|
// something went wrong during the query,
|
||||||
|
// maybe the connection was lost, so we just return
|
||||||
|
// a pending status, the caller can retry later
|
||||||
|
return {
|
||||||
|
status: 'PENDING',
|
||||||
|
preImage: null,
|
||||||
|
error: ''
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const tx = out.data.me.defaultAccount.walletById.transactionsByPaymentRequest.find(t => t.direction === 'SEND')
|
||||||
|
if (!tx) {
|
||||||
|
// the transaction was not found, something went wrong
|
||||||
|
return {
|
||||||
|
status: 'FAILED',
|
||||||
|
preImage: null,
|
||||||
|
error: 'transaction not found'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const status = tx.status
|
||||||
|
const preImage = tx.settlementVia.preImage
|
||||||
|
return {
|
||||||
|
status,
|
||||||
|
preImage,
|
||||||
|
error: ''
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function getWallet (authToken, currency) {
|
||||||
|
const out = await request(authToken, `
|
||||||
|
query me {
|
||||||
|
me {
|
||||||
|
defaultAccount {
|
||||||
|
wallets {
|
||||||
|
id
|
||||||
|
walletCurrency
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`, {})
|
||||||
|
const wallets = out.data.me.defaultAccount.wallets
|
||||||
|
for (const wallet of wallets) {
|
||||||
|
if (wallet.walletCurrency === currency) {
|
||||||
|
return wallet
|
||||||
|
}
|
||||||
|
}
|
||||||
|
throw new Error(`wallet ${currency} not found`)
|
||||||
|
}
|
||||||
|
|
||||||
|
async function request (authToken, query, variables = {}) {
|
||||||
|
const options = {
|
||||||
|
method: 'POST',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
'X-API-KEY': authToken
|
||||||
|
},
|
||||||
|
body: JSON.stringify({ query, variables })
|
||||||
|
}
|
||||||
|
const res = await fetch(galoyBlinkUrl, options)
|
||||||
|
if (res.status >= 400 && res.status <= 599) {
|
||||||
|
if (res.status === 401) {
|
||||||
|
throw new Error('unauthorized')
|
||||||
|
} else {
|
||||||
|
throw new Error('API responded with HTTP ' + res.status)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return res.json()
|
||||||
|
}
|
34
wallets/blink/index.js
Normal file
34
wallets/blink/index.js
Normal file
@ -0,0 +1,34 @@
|
|||||||
|
import { blinkSchema } from '@/lib/validate'
|
||||||
|
|
||||||
|
export const galoyBlinkUrl = 'https://api.blink.sv/graphql'
|
||||||
|
export const galoyBlinkDashboardUrl = 'https://dashboard.blink.sv/'
|
||||||
|
|
||||||
|
export const name = 'blink'
|
||||||
|
|
||||||
|
export const fields = [
|
||||||
|
{
|
||||||
|
name: 'apiKey',
|
||||||
|
label: 'api key',
|
||||||
|
type: 'password',
|
||||||
|
help: `you can get an API key from [Blink Dashboard](${galoyBlinkDashboardUrl})`,
|
||||||
|
placeholder: 'blink_...'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'currency',
|
||||||
|
label: 'wallet type',
|
||||||
|
type: 'text',
|
||||||
|
help: 'the blink wallet to use (BTC or USD for stablesats)',
|
||||||
|
placeholder: 'BTC',
|
||||||
|
optional: true,
|
||||||
|
clear: true,
|
||||||
|
autoComplete: 'off'
|
||||||
|
}
|
||||||
|
]
|
||||||
|
|
||||||
|
export const card = {
|
||||||
|
title: 'Blink',
|
||||||
|
subtitle: 'use [Blink](https://blink.sv/) for payments',
|
||||||
|
badges: ['send only']
|
||||||
|
}
|
||||||
|
|
||||||
|
export const fieldValidation = blinkSchema
|
@ -5,5 +5,6 @@ import * as lnAddr from 'wallets/lightning-address/client'
|
|||||||
import * as cln from 'wallets/cln/client'
|
import * as cln from 'wallets/cln/client'
|
||||||
import * as lnd from 'wallets/lnd/client'
|
import * as lnd from 'wallets/lnd/client'
|
||||||
import * as webln from 'wallets/webln/client'
|
import * as webln from 'wallets/webln/client'
|
||||||
|
import * as blink from 'wallets/blink/client'
|
||||||
|
|
||||||
export default [nwc, lnbits, lnc, lnAddr, cln, lnd, webln]
|
export default [nwc, lnbits, lnc, lnAddr, cln, lnd, webln, blink]
|
||||||
|
@ -187,11 +187,11 @@ function useConfig (wallet) {
|
|||||||
// Not optimal UX but the trade-off is saving invalid configurations
|
// Not optimal UX but the trade-off is saving invalid configurations
|
||||||
// and maybe it's not that big of an issue.
|
// and maybe it's not that big of an issue.
|
||||||
if (hasClientConfig) {
|
if (hasClientConfig) {
|
||||||
const newClientConfig = extractClientConfig(wallet.fields, newConfig)
|
let newClientConfig = extractClientConfig(wallet.fields, newConfig)
|
||||||
|
|
||||||
let valid = true
|
let valid = true
|
||||||
try {
|
try {
|
||||||
await walletValidate(wallet, newClientConfig)
|
newClientConfig = await walletValidate(wallet, newClientConfig)
|
||||||
} catch {
|
} catch {
|
||||||
valid = false
|
valid = false
|
||||||
}
|
}
|
||||||
@ -204,11 +204,11 @@ function useConfig (wallet) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (hasServerConfig) {
|
if (hasServerConfig) {
|
||||||
const newServerConfig = extractServerConfig(wallet.fields, newConfig)
|
let newServerConfig = extractServerConfig(wallet.fields, newConfig)
|
||||||
|
|
||||||
let valid = true
|
let valid = true
|
||||||
try {
|
try {
|
||||||
await walletValidate(wallet, newServerConfig)
|
newServerConfig = await walletValidate(wallet, newServerConfig)
|
||||||
} catch {
|
} catch {
|
||||||
valid = false
|
valid = false
|
||||||
}
|
}
|
||||||
|
@ -1,3 +1,5 @@
|
|||||||
|
import { msatsToSats } from '@/lib/format'
|
||||||
|
|
||||||
export * from 'wallets/lnbits'
|
export * from 'wallets/lnbits'
|
||||||
|
|
||||||
export async function testConnectServer ({ url, invoiceKey }) {
|
export async function testConnectServer ({ url, invoiceKey }) {
|
||||||
@ -14,9 +16,12 @@ export async function createInvoice (
|
|||||||
headers.append('Content-Type', 'application/json')
|
headers.append('Content-Type', 'application/json')
|
||||||
headers.append('X-Api-Key', invoiceKey)
|
headers.append('X-Api-Key', invoiceKey)
|
||||||
|
|
||||||
|
// lnbits doesn't support msats so we have to floor to nearest sat
|
||||||
|
const sats = msatsToSats(msats)
|
||||||
|
|
||||||
const body = JSON.stringify({
|
const body = JSON.stringify({
|
||||||
amount: msats,
|
amount: sats,
|
||||||
unit: 'msat',
|
unit: 'sat',
|
||||||
expiry,
|
expiry,
|
||||||
memo: description,
|
memo: description,
|
||||||
out: false
|
out: false
|
||||||
|
@ -27,13 +27,11 @@ export async function autoWithdraw ({ data: { id }, models, lnd }) {
|
|||||||
SELECT EXISTS(
|
SELECT EXISTS(
|
||||||
SELECT *
|
SELECT *
|
||||||
FROM "Withdrawl"
|
FROM "Withdrawl"
|
||||||
WHERE "userId" = ${id} AND "autoWithdraw"
|
WHERE "userId" = ${id}
|
||||||
AND (status IS NULL
|
AND "autoWithdraw"
|
||||||
OR (
|
AND status IS DISTINCT FROM 'CONFIRMED'
|
||||||
status <> 'CONFIRMED' AND
|
AND now() < created_at + interval '1 hour'
|
||||||
now() < created_at + interval '1 hour' AND
|
AND "msatsFeePaying" >= ${maxFeeMsats}
|
||||||
"msatsFeePaying" >= ${maxFeeMsats}
|
|
||||||
))
|
|
||||||
)`
|
)`
|
||||||
|
|
||||||
if (pendingOrFailed.exists) return
|
if (pendingOrFailed.exists) return
|
||||||
|
Loading…
x
Reference in New Issue
Block a user