From 680cca6852e87c4ce4ebc6e43094e4a573ed56e6 Mon Sep 17 00:00:00 2001 From: boufni95 Date: Mon, 9 Mar 2026 18:53:15 +0000 Subject: [PATCH] refund swap info --- datasource.js | 6 ++++-- proto/autogenerated/client.md | 3 +++ proto/autogenerated/go/types.go | 3 +++ proto/autogenerated/ts/types.ts | 19 ++++++++++++++++-- proto/service/structs.proto | 4 ++++ src/services/lnd/swaps/swaps.ts | 4 ++++ src/services/metrics/index.ts | 2 -- src/services/storage/entity/InvoiceSwap.ts | 10 ++++++++-- .../1773082318982-refund_swap_info.ts | 20 +++++++++++++++++++ src/services/storage/migrations/runner.ts | 3 ++- src/services/storage/paymentStorage.ts | 9 +++++++++ 11 files changed, 74 insertions(+), 9 deletions(-) create mode 100644 src/services/storage/migrations/1773082318982-refund_swap_info.ts diff --git a/datasource.js b/datasource.js index 4badca25..924e667c 100644 --- a/datasource.js +++ b/datasource.js @@ -66,6 +66,7 @@ import { InvoiceSwaps1769529793283 } from './build/src/services/storage/migratio import { InvoiceSwapsFixes1769805357459 } from './build/src/services/storage/migrations/1769805357459-invoice_swaps_fixes.js' import { ApplicationUserTopicId1770038768784 } from './build/src/services/storage/migrations/1770038768784-application_user_topic_id.js' import { SwapTimestamps1771347307798 } from './build/src/services/storage/migrations/1771347307798-swap_timestamps.js' +import { TxSwapTimestamps1771878683383 } from './build/src/services/storage/migrations/1771878683383-tx_swap_timestamps.js' @@ -80,7 +81,8 @@ export default new DataSource({ InvoiceCallbackUrls1752425992291, OldSomethingLeftover1753106599604, UserReceivingInvoiceIdx1753109184611, AppUserDevice1753285173175, UserAccess1759426050669, AddBlindToUserOffer1760000000000, ApplicationAvatarUrl1761000001000, AdminSettings1761683639419, TxSwap1762890527098, TxSwapAddress1764779178945, ClinkRequester1765497600000, TrackedProviderHeight1766504040000, SwapsServiceUrl1768413055036, - InvoiceSwaps1769529793283, InvoiceSwapsFixes1769805357459, ApplicationUserTopicId1770038768784, SwapTimestamps1771347307798 + InvoiceSwaps1769529793283, InvoiceSwapsFixes1769805357459, ApplicationUserTopicId1770038768784, SwapTimestamps1771347307798, + TxSwapTimestamps1771878683383 ], @@ -89,4 +91,4 @@ export default new DataSource({ TrackedProvider, InviteToken, DebitAccess, UserOffer, ManagementGrant, AppUserDevice, UserAccess, AdminSettings, TransactionSwap, InvoiceSwap], // synchronize: true, }) -//npx typeorm migration:generate ./src/services/storage/migrations/tx_swap_timestamps -d ./datasource.js \ No newline at end of file +//npx typeorm migration:generate ./src/services/storage/migrations/refund_swap_info -d ./datasource.js \ No newline at end of file diff --git a/proto/autogenerated/client.md b/proto/autogenerated/client.md index d42fc9ac..569f4328 100644 --- a/proto/autogenerated/client.md +++ b/proto/autogenerated/client.md @@ -1431,6 +1431,9 @@ The nostr server will send back a message response, and inside the body there wi - __failure_reason__: _string_ *this field is optional - __operation_payment__: _[UserOperation](#UserOperation)_ *this field is optional - __quote__: _[InvoiceSwapQuote](#InvoiceSwapQuote)_ + - __refund_address__: _string_ *this field is optional + - __refund_at_unix__: _number_ *this field is optional + - __refund_tx_id__: _string_ *this field is optional ### InvoiceSwapQuote - __address__: _string_ diff --git a/proto/autogenerated/go/types.go b/proto/autogenerated/go/types.go index cff46783..32b3a605 100644 --- a/proto/autogenerated/go/types.go +++ b/proto/autogenerated/go/types.go @@ -391,6 +391,9 @@ type InvoiceSwapOperation struct { Failure_reason string `json:"failure_reason"` Operation_payment *UserOperation `json:"operation_payment"` Quote *InvoiceSwapQuote `json:"quote"` + Refund_address string `json:"refund_address"` + Refund_at_unix int64 `json:"refund_at_unix"` + Refund_tx_id string `json:"refund_tx_id"` } type InvoiceSwapQuote struct { Address string `json:"address"` diff --git a/proto/autogenerated/ts/types.ts b/proto/autogenerated/ts/types.ts index 90cd1724..9b24aabb 100644 --- a/proto/autogenerated/ts/types.ts +++ b/proto/autogenerated/ts/types.ts @@ -2270,15 +2270,21 @@ export type InvoiceSwapOperation = { failure_reason?: string operation_payment?: UserOperation quote: InvoiceSwapQuote + refund_address?: string + refund_at_unix?: number + refund_tx_id?: string } -export type InvoiceSwapOperationOptionalField = 'completed_at_unix' | 'failure_reason' | 'operation_payment' -export const InvoiceSwapOperationOptionalFields: InvoiceSwapOperationOptionalField[] = ['completed_at_unix', 'failure_reason', 'operation_payment'] +export type InvoiceSwapOperationOptionalField = 'completed_at_unix' | 'failure_reason' | 'operation_payment' | 'refund_address' | 'refund_at_unix' | 'refund_tx_id' +export const InvoiceSwapOperationOptionalFields: InvoiceSwapOperationOptionalField[] = ['completed_at_unix', 'failure_reason', 'operation_payment', 'refund_address', 'refund_at_unix', 'refund_tx_id'] export type InvoiceSwapOperationOptions = OptionsBaseMessage & { checkOptionalsAreSet?: InvoiceSwapOperationOptionalField[] completed_at_unix_CustomCheck?: (v?: number) => boolean failure_reason_CustomCheck?: (v?: string) => boolean operation_payment_Options?: UserOperationOptions quote_Options?: InvoiceSwapQuoteOptions + refund_address_CustomCheck?: (v?: string) => boolean + refund_at_unix_CustomCheck?: (v?: number) => boolean + refund_tx_id_CustomCheck?: (v?: string) => boolean } export const InvoiceSwapOperationValidate = (o?: InvoiceSwapOperation, opts: InvoiceSwapOperationOptions = {}, path: string = 'InvoiceSwapOperation::root.'): Error | null => { if (opts.checkOptionalsAreSet && opts.allOptionalsAreSet) return new Error(path + ': only one of checkOptionalsAreSet or allOptionalNonDefault can be set for each message') @@ -2300,6 +2306,15 @@ export const InvoiceSwapOperationValidate = (o?: InvoiceSwapOperation, opts: Inv if (quoteErr !== null) return quoteErr + if ((o.refund_address || opts.allOptionalsAreSet || opts.checkOptionalsAreSet?.includes('refund_address')) && typeof o.refund_address !== 'string') return new Error(`${path}.refund_address: is not a string`) + if (opts.refund_address_CustomCheck && !opts.refund_address_CustomCheck(o.refund_address)) return new Error(`${path}.refund_address: custom check failed`) + + if ((o.refund_at_unix || opts.allOptionalsAreSet || opts.checkOptionalsAreSet?.includes('refund_at_unix')) && typeof o.refund_at_unix !== 'number') return new Error(`${path}.refund_at_unix: is not a number`) + if (opts.refund_at_unix_CustomCheck && !opts.refund_at_unix_CustomCheck(o.refund_at_unix)) return new Error(`${path}.refund_at_unix: custom check failed`) + + if ((o.refund_tx_id || opts.allOptionalsAreSet || opts.checkOptionalsAreSet?.includes('refund_tx_id')) && typeof o.refund_tx_id !== 'string') return new Error(`${path}.refund_tx_id: is not a string`) + if (opts.refund_tx_id_CustomCheck && !opts.refund_tx_id_CustomCheck(o.refund_tx_id)) return new Error(`${path}.refund_tx_id: custom check failed`) + return null } diff --git a/proto/service/structs.proto b/proto/service/structs.proto index 3d64ef67..c98e6607 100644 --- a/proto/service/structs.proto +++ b/proto/service/structs.proto @@ -926,6 +926,10 @@ message InvoiceSwapOperation { optional UserOperation operation_payment = 2; optional string failure_reason = 3; optional int64 completed_at_unix = 6; + + optional string refund_address = 7; + optional int64 refund_at_unix = 8; + optional string refund_tx_id = 9; } message InvoiceSwapsList { diff --git a/src/services/lnd/swaps/swaps.ts b/src/services/lnd/swaps/swaps.ts index a48c5c81..0360a879 100644 --- a/src/services/lnd/swaps/swaps.ts +++ b/src/services/lnd/swaps/swaps.ts @@ -99,6 +99,9 @@ export class Swaps { quote: this.mapInvoiceSwapQuote(s), failure_reason: s.failure_reason, completed_at_unix: s.completed_at_unix || 1, + refund_address: s.refund_address, + refund_at_unix: s.refund_at_unix, + refund_tx_id: s.refund_tx_id, })) return { current_block_height: currentBlockHeight, @@ -132,6 +135,7 @@ export class Swaps { if (!result.ok) { throw new Error(result.error) } + await this.storage.paymentStorage.UpdateRefundInvoiceSwap(swapOperationId, refundAddress, result.publish.txId) if (result.publish.done) { return { published: true, txId: result.publish.txId } } diff --git a/src/services/metrics/index.ts b/src/services/metrics/index.ts index 9e23b665..3b636f20 100644 --- a/src/services/metrics/index.ts +++ b/src/services/metrics/index.ts @@ -183,8 +183,6 @@ export default class Handler { }) } - - /* addTrackedMetric = (appId: string, method: string, metric: Uint8Array) => { if (!this.metaReady) { throw new Error("meta metrics not ready") diff --git a/src/services/storage/entity/InvoiceSwap.ts b/src/services/storage/entity/InvoiceSwap.ts index 746756b8..1ccba147 100644 --- a/src/services/storage/entity/InvoiceSwap.ts +++ b/src/services/storage/entity/InvoiceSwap.ts @@ -80,8 +80,14 @@ export class InvoiceSwap { @Column({ default: "", type: "text" }) lockup_tx_hex: string - /* @Column({ default: "" }) - address_paid: string */ + @Column({ default: "" }) + refund_address: string + + @Column({ default: "" }) + refund_at_unix: number + + @Column({ default: "" }) + refund_tx_id: string @Column({ default: "" }) service_url: string diff --git a/src/services/storage/migrations/1773082318982-refund_swap_info.ts b/src/services/storage/migrations/1773082318982-refund_swap_info.ts new file mode 100644 index 00000000..b9058c10 --- /dev/null +++ b/src/services/storage/migrations/1773082318982-refund_swap_info.ts @@ -0,0 +1,20 @@ +import { MigrationInterface, QueryRunner } from "typeorm"; + +export class RefundSwapInfo1773082318982 implements MigrationInterface { + name = 'RefundSwapInfo1773082318982' + + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query(`CREATE TABLE "temporary_invoice_swap" ("swap_operation_id" varchar PRIMARY KEY NOT NULL, "app_user_id" varchar NOT NULL, "swap_quote_id" varchar NOT NULL, "swap_tree" varchar NOT NULL, "claim_public_key" varchar NOT NULL, "payment_hash" varchar NOT NULL, "timeout_block_height" integer NOT NULL, "invoice" varchar NOT NULL, "invoice_amount" integer NOT NULL, "transaction_amount" integer NOT NULL, "swap_fee_sats" integer NOT NULL, "chain_fee_sats" integer NOT NULL, "ephemeral_public_key" varchar NOT NULL, "address" varchar NOT NULL, "ephemeral_private_key" varchar NOT NULL, "used" boolean NOT NULL DEFAULT (0), "preimage" varchar NOT NULL DEFAULT (''), "failure_reason" varchar NOT NULL DEFAULT (''), "tx_id" varchar NOT NULL DEFAULT (''), "service_url" varchar NOT NULL DEFAULT (''), "created_at" datetime NOT NULL DEFAULT (datetime('now')), "updated_at" datetime NOT NULL DEFAULT (datetime('now')), "lockup_tx_hex" text NOT NULL DEFAULT (''), "completed_at_unix" integer NOT NULL DEFAULT (0), "paid_at_unix" integer NOT NULL DEFAULT (0), "refund_address" varchar NOT NULL DEFAULT (''), "refund_at_unix" integer NOT NULL DEFAULT (''), "refund_tx_id" varchar NOT NULL DEFAULT (''))`); + await queryRunner.query(`INSERT INTO "temporary_invoice_swap"("swap_operation_id", "app_user_id", "swap_quote_id", "swap_tree", "claim_public_key", "payment_hash", "timeout_block_height", "invoice", "invoice_amount", "transaction_amount", "swap_fee_sats", "chain_fee_sats", "ephemeral_public_key", "address", "ephemeral_private_key", "used", "preimage", "failure_reason", "tx_id", "service_url", "created_at", "updated_at", "lockup_tx_hex", "completed_at_unix", "paid_at_unix") SELECT "swap_operation_id", "app_user_id", "swap_quote_id", "swap_tree", "claim_public_key", "payment_hash", "timeout_block_height", "invoice", "invoice_amount", "transaction_amount", "swap_fee_sats", "chain_fee_sats", "ephemeral_public_key", "address", "ephemeral_private_key", "used", "preimage", "failure_reason", "tx_id", "service_url", "created_at", "updated_at", "lockup_tx_hex", "completed_at_unix", "paid_at_unix" FROM "invoice_swap"`); + await queryRunner.query(`DROP TABLE "invoice_swap"`); + await queryRunner.query(`ALTER TABLE "temporary_invoice_swap" RENAME TO "invoice_swap"`); + } + + public async down(queryRunner: QueryRunner): Promise { + await queryRunner.query(`ALTER TABLE "invoice_swap" RENAME TO "temporary_invoice_swap"`); + await queryRunner.query(`CREATE TABLE "invoice_swap" ("swap_operation_id" varchar PRIMARY KEY NOT NULL, "app_user_id" varchar NOT NULL, "swap_quote_id" varchar NOT NULL, "swap_tree" varchar NOT NULL, "claim_public_key" varchar NOT NULL, "payment_hash" varchar NOT NULL, "timeout_block_height" integer NOT NULL, "invoice" varchar NOT NULL, "invoice_amount" integer NOT NULL, "transaction_amount" integer NOT NULL, "swap_fee_sats" integer NOT NULL, "chain_fee_sats" integer NOT NULL, "ephemeral_public_key" varchar NOT NULL, "address" varchar NOT NULL, "ephemeral_private_key" varchar NOT NULL, "used" boolean NOT NULL DEFAULT (0), "preimage" varchar NOT NULL DEFAULT (''), "failure_reason" varchar NOT NULL DEFAULT (''), "tx_id" varchar NOT NULL DEFAULT (''), "service_url" varchar NOT NULL DEFAULT (''), "created_at" datetime NOT NULL DEFAULT (datetime('now')), "updated_at" datetime NOT NULL DEFAULT (datetime('now')), "lockup_tx_hex" text NOT NULL DEFAULT (''), "completed_at_unix" integer NOT NULL DEFAULT (0), "paid_at_unix" integer NOT NULL DEFAULT (0))`); + await queryRunner.query(`INSERT INTO "invoice_swap"("swap_operation_id", "app_user_id", "swap_quote_id", "swap_tree", "claim_public_key", "payment_hash", "timeout_block_height", "invoice", "invoice_amount", "transaction_amount", "swap_fee_sats", "chain_fee_sats", "ephemeral_public_key", "address", "ephemeral_private_key", "used", "preimage", "failure_reason", "tx_id", "service_url", "created_at", "updated_at", "lockup_tx_hex", "completed_at_unix", "paid_at_unix") SELECT "swap_operation_id", "app_user_id", "swap_quote_id", "swap_tree", "claim_public_key", "payment_hash", "timeout_block_height", "invoice", "invoice_amount", "transaction_amount", "swap_fee_sats", "chain_fee_sats", "ephemeral_public_key", "address", "ephemeral_private_key", "used", "preimage", "failure_reason", "tx_id", "service_url", "created_at", "updated_at", "lockup_tx_hex", "completed_at_unix", "paid_at_unix" FROM "temporary_invoice_swap"`); + await queryRunner.query(`DROP TABLE "temporary_invoice_swap"`); + } + +} diff --git a/src/services/storage/migrations/runner.ts b/src/services/storage/migrations/runner.ts index c78af2eb..c2150831 100644 --- a/src/services/storage/migrations/runner.ts +++ b/src/services/storage/migrations/runner.ts @@ -30,6 +30,7 @@ import { InvoiceSwapsFixes1769805357459 } from './1769805357459-invoice_swaps_fi import { ApplicationUserTopicId1770038768784 } from './1770038768784-application_user_topic_id.js' import { SwapTimestamps1771347307798 } from './1771347307798-swap_timestamps.js' import { TxSwapTimestamps1771878683383 } from './1771878683383-tx_swap_timestamps.js' +import { RefundSwapInfo1773082318982 } from './1773082318982-refund_swap_info.js' import { LndMetrics1703170330183 } from './1703170330183-lnd_metrics.js' import { ChannelRouting1709316653538 } from './1709316653538-channel_routing.js' @@ -51,7 +52,7 @@ export const allMigrations = [Initial1703170309875, LspOrder1718387847693, Liqui UserAccess1759426050669, AddBlindToUserOffer1760000000000, ApplicationAvatarUrl1761000001000, AdminSettings1761683639419, TxSwap1762890527098, TxSwapAddress1764779178945, ClinkRequester1765497600000, TrackedProviderHeight1766504040000, SwapsServiceUrl1768413055036, InvoiceSwaps1769529793283, InvoiceSwapsFixes1769805357459, ApplicationUserTopicId1770038768784, SwapTimestamps1771347307798, - TxSwapTimestamps1771878683383] + TxSwapTimestamps1771878683383, RefundSwapInfo1773082318982] export const allMetricsMigrations = [LndMetrics1703170330183, ChannelRouting1709316653538, HtlcCount1724266887195, BalanceEvents1724860966825, diff --git a/src/services/storage/paymentStorage.ts b/src/services/storage/paymentStorage.ts index af96b6c5..af2de96a 100644 --- a/src/services/storage/paymentStorage.ts +++ b/src/services/storage/paymentStorage.ts @@ -655,6 +655,15 @@ export default class { return swaps.filter(s => !!s.tx_id) } + async UpdateRefundInvoiceSwap(swapOperationId: string, refundAddress: string, refundTxId: string, txId?: string) { + const now = Math.floor(Date.now() / 1000) + return this.dbs.Update('InvoiceSwap', { swap_operation_id: swapOperationId }, { + refund_address: refundAddress, + refund_at_unix: now, + refund_tx_id: refundTxId, + }, txId) + } + async GetRefundableInvoiceSwap(swapOperationId: string, txId?: string) { const swap = await this.dbs.FindOne('InvoiceSwap', { where: { swap_operation_id: swapOperationId } }, txId) if (!swap || !swap.tx_id) {