From 2d0c49a51785b493bbfa4ff359ee56b0de45ac65 Mon Sep 17 00:00:00 2001 From: Riccardo Balbo Date: Thu, 19 Dec 2024 16:24:53 +0100 Subject: [PATCH 1/3] zbd --- fragments/wallet.js | 3 + .../migration.sql | 24 +++++++ prisma/schema.prisma | 11 ++++ public/wallets/zbd-dark.svg | 1 + public/wallets/zbd.svg | 1 + wallets/client.js | 3 +- wallets/server.js | 3 +- wallets/zebedee/client.js | 63 +++++++++++++++++++ wallets/zebedee/index.js | 42 +++++++++++++ wallets/zebedee/server.js | 58 +++++++++++++++++ 10 files changed, 207 insertions(+), 2 deletions(-) create mode 100644 prisma/migrations/20241219120508_zebedee_attachment/migration.sql create mode 100644 public/wallets/zbd-dark.svg create mode 100644 public/wallets/zbd.svg create mode 100644 wallets/zebedee/client.js create mode 100644 wallets/zebedee/index.js create mode 100644 wallets/zebedee/server.js diff --git a/fragments/wallet.js b/fragments/wallet.js index 6f84f4afd..10e8ac060 100644 --- a/fragments/wallet.js +++ b/fragments/wallet.js @@ -169,6 +169,9 @@ export const WALLET_FIELDS = gql` apiKeyRecv currencyRecv } + ... on WalletZebedee { + gamerTagId + } } } ` diff --git a/prisma/migrations/20241219120508_zebedee_attachment/migration.sql b/prisma/migrations/20241219120508_zebedee_attachment/migration.sql new file mode 100644 index 000000000..416595e3e --- /dev/null +++ b/prisma/migrations/20241219120508_zebedee_attachment/migration.sql @@ -0,0 +1,24 @@ +-- AlterEnum +ALTER TYPE "WalletType" ADD VALUE 'ZEBEDEE'; + +-- CreateTable +CREATE TABLE "WalletZebedee" ( + "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, + "gamerTagId" TEXT, + + CONSTRAINT "WalletZebedee_pkey" PRIMARY KEY ("id") +); + +-- CreateIndex +CREATE UNIQUE INDEX "WalletZebedee_walletId_key" ON "WalletZebedee"("walletId"); + +-- AddForeignKey +ALTER TABLE "WalletZebedee" ADD CONSTRAINT "WalletZebedee_walletId_fkey" FOREIGN KEY ("walletId") REFERENCES "Wallet"("id") ON DELETE CASCADE ON UPDATE CASCADE; + +-- Update wallet json +CREATE TRIGGER wallet_zebedee_as_jsonb +AFTER INSERT OR UPDATE ON "WalletZebedee" +FOR EACH ROW EXECUTE PROCEDURE wallet_wallet_type_as_jsonb(); \ No newline at end of file diff --git a/prisma/schema.prisma b/prisma/schema.prisma index 59685b931..0db4ab4bc 100644 --- a/prisma/schema.prisma +++ b/prisma/schema.prisma @@ -189,6 +189,7 @@ enum WalletType { BLINK LNC WEBLN + ZEBEDEE } model Wallet { @@ -216,6 +217,7 @@ model Wallet { walletNWC WalletNWC? walletPhoenixd WalletPhoenixd? walletBlink WalletBlink? + walletZebedee WalletZebedee? vaultEntries VaultEntry[] @relation("VaultEntries") withdrawals Withdrawl[] @@ -325,6 +327,15 @@ model WalletPhoenixd { secondaryPassword String? } +model WalletZebedee { + 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") + gamerTagId String? +} + model Mute { muterId Int mutedId Int diff --git a/public/wallets/zbd-dark.svg b/public/wallets/zbd-dark.svg new file mode 100644 index 000000000..d2b03bd9a --- /dev/null +++ b/public/wallets/zbd-dark.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/public/wallets/zbd.svg b/public/wallets/zbd.svg new file mode 100644 index 000000000..b92c3ff5c --- /dev/null +++ b/public/wallets/zbd.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/wallets/client.js b/wallets/client.js index 8bd44698f..e5e5c3287 100644 --- a/wallets/client.js +++ b/wallets/client.js @@ -7,5 +7,6 @@ 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' +import * as zebedee from '@/wallets/zebedee/client' -export default [nwc, lnbits, lnc, lnAddr, cln, lnd, webln, blink, phoenixd] +export default [nwc, lnbits, lnc, lnAddr, cln, lnd, webln, blink, phoenixd, zebedee] diff --git a/wallets/server.js b/wallets/server.js index f14e9fb36..0a39bc96e 100644 --- a/wallets/server.js +++ b/wallets/server.js @@ -6,6 +6,7 @@ import * as lnbits from '@/wallets/lnbits/server' import * as nwc from '@/wallets/nwc/server' import * as phoenixd from '@/wallets/phoenixd/server' import * as blink from '@/wallets/blink/server' +import * as zebedee from '@/wallets/zebedee/server' // we import only the metadata of client side wallets import * as lnc from '@/wallets/lnc' @@ -20,7 +21,7 @@ import { timeoutSignal, withTimeout } from '@/lib/time' import { canReceive } from './common' import wrapInvoice from './wrap' -export default [lnd, cln, lnAddr, lnbits, nwc, phoenixd, blink, lnc, webln] +export default [lnd, cln, lnAddr, lnbits, nwc, phoenixd, blink, lnc, webln, zebedee] const MAX_PENDING_INVOICES_PER_WALLET = 25 diff --git a/wallets/zebedee/client.js b/wallets/zebedee/client.js new file mode 100644 index 000000000..900a287c6 --- /dev/null +++ b/wallets/zebedee/client.js @@ -0,0 +1,63 @@ +import { API_URL, PREIMAGE_AWAIT_TIMEOUT_MS } from '@/wallets/zebedee' +import { assertContentTypeJson } from '@/lib/url' +import { callWithTimeout } from '@/lib/time' +import { fetchWithTimeout } from '@/lib/fetch' + +export * from '@/wallets/zebedee' + +export async function testSendPayment ({ apiKey }, { signal }) { + const wallet = await apiCall('wallet', { apiKey, method: 'GET' }, { signal }) + if (!wallet.data) throw new Error('wallet not found') +} + +export async function sendPayment (bolt11, { apiKey }, { signal }) { + const res = await apiCall('payments', { body: { invoice: bolt11 }, apiKey }, { signal }) + const { id, preimage } = res?.data + if (preimage) return preimage + // the api might return before the invoice is paid, so we'll wait for the preimage + return await waitForPreimage(id, { apiKey }, { signal }) +} + +async function waitForPreimage (id, { apiKey }, { signal }) { + return await callWithTimeout(async () => { + let preimage + while (true) { + const res = await apiCall('payments/{id}', { body: { id }, apiKey, method: 'GET' }, { signal }) + preimage = res?.data?.preimage + if (preimage) break + await new Promise(resolve => setTimeout(resolve, 10)) + } + return preimage + }, PREIMAGE_AWAIT_TIMEOUT_MS) +} + +export async function apiCall (api, { body, apiKey, method = 'POST' }, { signal }) { + const headers = { + apikey: apiKey, + 'Content-Type': 'application/json' + } + if (method === 'GET' && body) { + for (const [k, v] of Object.entries(body)) { + api = api.replace('{' + k + '}', v) + } + } + const res = await fetchWithTimeout(API_URL + api, { + method, + headers, + signal, + body: method === 'POST' ? JSON.stringify(body) : undefined + }) + // https://zbd.dev/api-reference/errors + if (res.status !== 200) { + let error + try { + assertContentTypeJson(res) + const json = await res.json() + if (json?.message) error = json.message + } catch (e) { + error = res.statusText || 'error ' + res.status + } + throw new Error(error) + } + return res.json() +} diff --git a/wallets/zebedee/index.js b/wallets/zebedee/index.js new file mode 100644 index 000000000..a88af1225 --- /dev/null +++ b/wallets/zebedee/index.js @@ -0,0 +1,42 @@ +import { string } from '@/lib/yup' + +export const PREIMAGE_AWAIT_TIMEOUT_MS = 1_200 +export const STATIC_CHARGE_URL = 'https://api.zebedee.io/v0/process-static-charges/' +export const DASHBOARD_URL = 'https://dashboard.zebedee.io/' +export const GAMER_TAG_LNADDR_BASEURL = 'https://zbd.gg/.well-known/lnurlp/' +export const API_URL = 'https://api.zebedee.io/v0/' +export const ZEBEDEE_LNDOMAIN = 'zbd.gg' + +export const name = 'zebedee' +export const walletType = 'ZEBEDEE' +export const walletField = 'walletZebedee' + +export const fields = [ + { + name: 'apiKey', + label: 'api key', + type: 'password', + optional: 'for sending', + help: `you can get an API key from [Zebedee Dashboard](${DASHBOARD_URL}) from \n\`Project->API->Live\``, + clientOnly: true, + requiredWithout: 'gamerTagId', + validate: string() + }, + { + name: 'gamerTagId', + label: 'gamer tag or id', + type: 'text', + optional: 'for receiving', + help: `you can find your Gamertag in the [Zebedee Dashboard](${DASHBOARD_URL}) under \n\`Account->Gamertag\`\n section, or in the Zebedee app on the Wallet card.\nNote: You can also use your \`@${ZEBEDEE_LNDOMAIN}\` Lightning address here.`, + serverOnly: true, + requiredWithout: 'apiKey', + validate: string() + } +] + +export const card = { + title: 'Zebedee', + subtitle: 'use [Zebedee](https://zebedee.io) for payments', + image: { src: '/wallets/zbd.svg' } + +} diff --git a/wallets/zebedee/server.js b/wallets/zebedee/server.js new file mode 100644 index 000000000..810b0576a --- /dev/null +++ b/wallets/zebedee/server.js @@ -0,0 +1,58 @@ +import { GAMER_TAG_LNADDR_BASEURL, STATIC_CHARGE_URL, ZEBEDEE_LNDOMAIN } from '@/wallets/zebedee' +import { fetchWithTimeout } from '@/lib/fetch' +import { assertContentTypeJson } from '@/lib/url' + +export * from '@/wallets/zebedee' + +async function fetchJson (url, { signal }) { + let res = await fetchWithTimeout(url, { signal }) + assertContentTypeJson(res) + if (!res.ok) { + res.text().catch(() => {}) + throw new Error(res.statusText || 'error ' + res.status) + } + res = await res.json() + if (res.status?.toLowerCase() === 'error') { + throw new Error(res.reason) + } + return res +} + +function isGamerTag (value) { + if (value.endsWith('@' + ZEBEDEE_LNDOMAIN)) return true + return value.length > 0 && value.length < 30 +} + +export async function fetchGamerId (value, { signal }) { + if (isGamerTag(value)) { + const [gamerTag, domain] = value.split('@') + if (domain && domain !== ZEBEDEE_LNDOMAIN) throw new Error(`invalid gamer tag: not a @${ZEBEDEE_LNDOMAIN} lightning address`) + const url = GAMER_TAG_LNADDR_BASEURL + gamerTag + try { + const res = await fetchJson(url, { signal }) + const callback = res.callback + if (!callback) throw new Error('cannot fetch gamer id: ' + (res.statusText || 'error ' + res.status)) + const gamerId = callback.substring(callback.lastIndexOf('/') + 1) + return gamerId + } catch (e) { + throw new Error('cannot fetch gamer id: ' + e.message) + } + } + return value +} + +export async function testCreateInvoice (credentials, { signal }) { + credentials.gamerTagId = await fetchGamerId(credentials.gamerTagId, { signal }) + return await createInvoice({ msats: 1000, expiry: 1 }, credentials, { signal }) +} + +export async function createInvoice ({ msats, description, expiry }, { gamerTagId }, { signal }) { + try { + const url = STATIC_CHARGE_URL + gamerTagId + '?amount=' + msats + '&comment=' + description + const res = await fetchJson(url, { signal }) + if (!res.pr) throw new Error('cannot fetch invoice') + return res.pr + } catch (e) { + throw new Error(e.message) + } +} From 0949059034383cd9aef74c0dc91c9802d4add144 Mon Sep 17 00:00:00 2001 From: Riccardo Balbo Date: Mon, 6 Jan 2025 20:25:43 +0100 Subject: [PATCH 2/3] zbd send only attachment --- fragments/wallet.js | 3 - .../migration.sql | 24 -------- .../migration.sql | 2 + prisma/schema.prisma | 10 ---- wallets/server.js | 2 +- wallets/zebedee/client.js | 57 +++++++++--------- wallets/zebedee/index.js | 18 +----- wallets/zebedee/server.js | 58 ------------------- 8 files changed, 35 insertions(+), 139 deletions(-) delete mode 100644 prisma/migrations/20241219120508_zebedee_attachment/migration.sql create mode 100644 prisma/migrations/20250106164328_zebedee_sender/migration.sql delete mode 100644 wallets/zebedee/server.js diff --git a/fragments/wallet.js b/fragments/wallet.js index 10e8ac060..6f84f4afd 100644 --- a/fragments/wallet.js +++ b/fragments/wallet.js @@ -169,9 +169,6 @@ export const WALLET_FIELDS = gql` apiKeyRecv currencyRecv } - ... on WalletZebedee { - gamerTagId - } } } ` diff --git a/prisma/migrations/20241219120508_zebedee_attachment/migration.sql b/prisma/migrations/20241219120508_zebedee_attachment/migration.sql deleted file mode 100644 index 416595e3e..000000000 --- a/prisma/migrations/20241219120508_zebedee_attachment/migration.sql +++ /dev/null @@ -1,24 +0,0 @@ --- AlterEnum -ALTER TYPE "WalletType" ADD VALUE 'ZEBEDEE'; - --- CreateTable -CREATE TABLE "WalletZebedee" ( - "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, - "gamerTagId" TEXT, - - CONSTRAINT "WalletZebedee_pkey" PRIMARY KEY ("id") -); - --- CreateIndex -CREATE UNIQUE INDEX "WalletZebedee_walletId_key" ON "WalletZebedee"("walletId"); - --- AddForeignKey -ALTER TABLE "WalletZebedee" ADD CONSTRAINT "WalletZebedee_walletId_fkey" FOREIGN KEY ("walletId") REFERENCES "Wallet"("id") ON DELETE CASCADE ON UPDATE CASCADE; - --- Update wallet json -CREATE TRIGGER wallet_zebedee_as_jsonb -AFTER INSERT OR UPDATE ON "WalletZebedee" -FOR EACH ROW EXECUTE PROCEDURE wallet_wallet_type_as_jsonb(); \ No newline at end of file diff --git a/prisma/migrations/20250106164328_zebedee_sender/migration.sql b/prisma/migrations/20250106164328_zebedee_sender/migration.sql new file mode 100644 index 000000000..973f61eac --- /dev/null +++ b/prisma/migrations/20250106164328_zebedee_sender/migration.sql @@ -0,0 +1,2 @@ +-- AlterEnum +ALTER TYPE "WalletType" ADD VALUE 'ZEBEDEE'; diff --git a/prisma/schema.prisma b/prisma/schema.prisma index 0db4ab4bc..af01507e4 100644 --- a/prisma/schema.prisma +++ b/prisma/schema.prisma @@ -217,7 +217,6 @@ model Wallet { walletNWC WalletNWC? walletPhoenixd WalletPhoenixd? walletBlink WalletBlink? - walletZebedee WalletZebedee? vaultEntries VaultEntry[] @relation("VaultEntries") withdrawals Withdrawl[] @@ -327,15 +326,6 @@ model WalletPhoenixd { secondaryPassword String? } -model WalletZebedee { - 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") - gamerTagId String? -} - model Mute { muterId Int mutedId Int diff --git a/wallets/server.js b/wallets/server.js index 0a39bc96e..fd96aa0cb 100644 --- a/wallets/server.js +++ b/wallets/server.js @@ -6,11 +6,11 @@ import * as lnbits from '@/wallets/lnbits/server' import * as nwc from '@/wallets/nwc/server' import * as phoenixd from '@/wallets/phoenixd/server' import * as blink from '@/wallets/blink/server' -import * as zebedee from '@/wallets/zebedee/server' // we import only the metadata of client side wallets import * as lnc from '@/wallets/lnc' import * as webln from '@/wallets/webln' +import * as zebedee from '@/wallets/zebedee' import { walletLogger } from '@/api/resolvers/wallet' import walletDefs from '@/wallets/server' diff --git a/wallets/zebedee/client.js b/wallets/zebedee/client.js index 900a287c6..8019a554d 100644 --- a/wallets/zebedee/client.js +++ b/wallets/zebedee/client.js @@ -1,8 +1,5 @@ -import { API_URL, PREIMAGE_AWAIT_TIMEOUT_MS } from '@/wallets/zebedee' +import { API_URL } from '@/wallets/zebedee' import { assertContentTypeJson } from '@/lib/url' -import { callWithTimeout } from '@/lib/time' -import { fetchWithTimeout } from '@/lib/fetch' - export * from '@/wallets/zebedee' export async function testSendPayment ({ apiKey }, { signal }) { @@ -19,45 +16,53 @@ export async function sendPayment (bolt11, { apiKey }, { signal }) { } async function waitForPreimage (id, { apiKey }, { signal }) { - return await callWithTimeout(async () => { - let preimage - while (true) { - const res = await apiCall('payments/{id}', { body: { id }, apiKey, method: 'GET' }, { signal }) - preimage = res?.data?.preimage - if (preimage) break - await new Promise(resolve => setTimeout(resolve, 10)) - } - return preimage - }, PREIMAGE_AWAIT_TIMEOUT_MS) + while (!signal.aborted) { + const res = await apiCall('payments/{id}', { body: { id }, apiKey, method: 'GET' }, { signal }) + + // return preimage if it's available + const preimage = res?.data?.preimage + if (preimage) return preimage + + // wait a before checking again + await new Promise(resolve => setTimeout(resolve, 30)) + } + return null } export async function apiCall (api, { body, apiKey, method = 'POST' }, { signal }) { - const headers = { - apikey: apiKey, - 'Content-Type': 'application/json' - } + // if get request, put params into the url if (method === 'GET' && body) { for (const [k, v] of Object.entries(body)) { - api = api.replace('{' + k + '}', v) + api = api.replace(`{${k}}`, v) } } - const res = await fetchWithTimeout(API_URL + api, { + + const res = await fetch(API_URL + api, { method, - headers, + headers: { + apikey: apiKey, + 'Content-Type': 'application/json' + }, signal, body: method === 'POST' ? JSON.stringify(body) : undefined }) - // https://zbd.dev/api-reference/errors - if (res.status !== 200) { + + // Catch errors + // ref: https://zbd.dev/api-reference/errors + if (res.status < 200 || res.status > 299) { + // try to extract the error message from the response let error try { assertContentTypeJson(res) const json = await res.json() if (json?.message) error = json.message } catch (e) { - error = res.statusText || 'error ' + res.status + console.log('failed to parse error', e) } - throw new Error(error) + // throw the error, if we don't have one, we try to use the request status + throw new Error(error ?? (res.statusText || `error ${res.status}`)) } - return res.json() + + assertContentTypeJson(res) + return await res.json() } diff --git a/wallets/zebedee/index.js b/wallets/zebedee/index.js index a88af1225..15b497804 100644 --- a/wallets/zebedee/index.js +++ b/wallets/zebedee/index.js @@ -1,11 +1,7 @@ import { string } from '@/lib/yup' -export const PREIMAGE_AWAIT_TIMEOUT_MS = 1_200 -export const STATIC_CHARGE_URL = 'https://api.zebedee.io/v0/process-static-charges/' export const DASHBOARD_URL = 'https://dashboard.zebedee.io/' -export const GAMER_TAG_LNADDR_BASEURL = 'https://zbd.gg/.well-known/lnurlp/' export const API_URL = 'https://api.zebedee.io/v0/' -export const ZEBEDEE_LNDOMAIN = 'zbd.gg' export const name = 'zebedee' export const walletType = 'ZEBEDEE' @@ -19,18 +15,7 @@ export const fields = [ optional: 'for sending', help: `you can get an API key from [Zebedee Dashboard](${DASHBOARD_URL}) from \n\`Project->API->Live\``, clientOnly: true, - requiredWithout: 'gamerTagId', - validate: string() - }, - { - name: 'gamerTagId', - label: 'gamer tag or id', - type: 'text', - optional: 'for receiving', - help: `you can find your Gamertag in the [Zebedee Dashboard](${DASHBOARD_URL}) under \n\`Account->Gamertag\`\n section, or in the Zebedee app on the Wallet card.\nNote: You can also use your \`@${ZEBEDEE_LNDOMAIN}\` Lightning address here.`, - serverOnly: true, - requiredWithout: 'apiKey', - validate: string() + validate: string().min(8, 'invalid api key').max(64, 'api key is too long') } ] @@ -38,5 +23,4 @@ export const card = { title: 'Zebedee', subtitle: 'use [Zebedee](https://zebedee.io) for payments', image: { src: '/wallets/zbd.svg' } - } diff --git a/wallets/zebedee/server.js b/wallets/zebedee/server.js deleted file mode 100644 index 810b0576a..000000000 --- a/wallets/zebedee/server.js +++ /dev/null @@ -1,58 +0,0 @@ -import { GAMER_TAG_LNADDR_BASEURL, STATIC_CHARGE_URL, ZEBEDEE_LNDOMAIN } from '@/wallets/zebedee' -import { fetchWithTimeout } from '@/lib/fetch' -import { assertContentTypeJson } from '@/lib/url' - -export * from '@/wallets/zebedee' - -async function fetchJson (url, { signal }) { - let res = await fetchWithTimeout(url, { signal }) - assertContentTypeJson(res) - if (!res.ok) { - res.text().catch(() => {}) - throw new Error(res.statusText || 'error ' + res.status) - } - res = await res.json() - if (res.status?.toLowerCase() === 'error') { - throw new Error(res.reason) - } - return res -} - -function isGamerTag (value) { - if (value.endsWith('@' + ZEBEDEE_LNDOMAIN)) return true - return value.length > 0 && value.length < 30 -} - -export async function fetchGamerId (value, { signal }) { - if (isGamerTag(value)) { - const [gamerTag, domain] = value.split('@') - if (domain && domain !== ZEBEDEE_LNDOMAIN) throw new Error(`invalid gamer tag: not a @${ZEBEDEE_LNDOMAIN} lightning address`) - const url = GAMER_TAG_LNADDR_BASEURL + gamerTag - try { - const res = await fetchJson(url, { signal }) - const callback = res.callback - if (!callback) throw new Error('cannot fetch gamer id: ' + (res.statusText || 'error ' + res.status)) - const gamerId = callback.substring(callback.lastIndexOf('/') + 1) - return gamerId - } catch (e) { - throw new Error('cannot fetch gamer id: ' + e.message) - } - } - return value -} - -export async function testCreateInvoice (credentials, { signal }) { - credentials.gamerTagId = await fetchGamerId(credentials.gamerTagId, { signal }) - return await createInvoice({ msats: 1000, expiry: 1 }, credentials, { signal }) -} - -export async function createInvoice ({ msats, description, expiry }, { gamerTagId }, { signal }) { - try { - const url = STATIC_CHARGE_URL + gamerTagId + '?amount=' + msats + '&comment=' + description - const res = await fetchJson(url, { signal }) - if (!res.pr) throw new Error('cannot fetch invoice') - return res.pr - } catch (e) { - throw new Error(e.message) - } -} From ab6676cd4b280bd03a976eb83af7c51868faac87 Mon Sep 17 00:00:00 2001 From: Riccardo Balbo Date: Mon, 6 Jan 2025 20:46:47 +0100 Subject: [PATCH 3/3] make code more explicit --- wallets/zebedee/client.js | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/wallets/zebedee/client.js b/wallets/zebedee/client.js index 8019a554d..d5375d72e 100644 --- a/wallets/zebedee/client.js +++ b/wallets/zebedee/client.js @@ -9,8 +9,11 @@ export async function testSendPayment ({ apiKey }, { signal }) { export async function sendPayment (bolt11, { apiKey }, { signal }) { const res = await apiCall('payments', { body: { invoice: bolt11 }, apiKey }, { signal }) - const { id, preimage } = res?.data + if (!res?.data) throw new Error('payment failed') + + const { id, preimage } = res.data if (preimage) return preimage + // the api might return before the invoice is paid, so we'll wait for the preimage return await waitForPreimage(id, { apiKey }, { signal }) } @@ -59,8 +62,10 @@ export async function apiCall (api, { body, apiKey, method = 'POST' }, { signal } catch (e) { console.log('failed to parse error', e) } + // throw the error, if we don't have one, we try to use the request status - throw new Error(error ?? (res.statusText || `error ${res.status}`)) + if (!error) error = res.statusText || `error ${res.status}` + throw new Error(error) } assertContentTypeJson(res)