diff --git a/datasource.js b/datasource.js index 081634d8..9c291b25 100644 --- a/datasource.js +++ b/datasource.js @@ -50,6 +50,7 @@ import { ClinkRequester1765497600000 } from './build/src/services/storage/migrat import { TrackedProviderHeight1766504040000 } from './build/src/services/storage/migrations/1766504040000-tracked_provider_height.js' import { SwapsServiceUrl1768413055036 } from './build/src/services/storage/migrations/1768413055036-swaps_service_url.js' import { InvoiceSwaps1769529793283 } from './build/src/services/storage/migrations/1769529793283-invoice_swaps.js' +import { InvoiceSwapsFixes1769805357459 } from './build/src/services/storage/migrations/1769805357459-invoice_swaps_fixes.js' export default new DataSource({ type: "better-sqlite3", @@ -59,11 +60,11 @@ export default new DataSource({ PaymentIndex1721760297610, DebitAccess1726496225078, DebitAccessFixes1726685229264, DebitToPub1727105758354, UserCbUrl1727112281043, UserOffer1733502626042, ManagementGrant1751307732346, InvoiceCallbackUrls1752425992291, OldSomethingLeftover1753106599604, UserReceivingInvoiceIdx1753109184611, AppUserDevice1753285173175, UserAccess1759426050669, AddBlindToUserOffer1760000000000, ApplicationAvatarUrl1761000001000, AdminSettings1761683639419, TxSwap1762890527098, - TxSwapAddress1764779178945, ClinkRequester1765497600000, TrackedProviderHeight1766504040000, SwapsServiceUrl1768413055036, InvoiceSwaps1769529793283], + TxSwapAddress1764779178945, ClinkRequester1765497600000, TrackedProviderHeight1766504040000, SwapsServiceUrl1768413055036, InvoiceSwaps1769529793283, InvoiceSwapsFixes1769805357459], entities: [User, UserReceivingInvoice, UserReceivingAddress, AddressReceivingTransaction, UserInvoicePayment, UserTransactionPayment, UserBasicAuth, UserEphemeralKey, Product, UserToUserPayment, Application, ApplicationUser, UserToUserPayment, LspOrder, LndNodeInfo, TrackedProvider, InviteToken, DebitAccess, UserOffer, ManagementGrant, AppUserDevice, UserAccess, AdminSettings, TransactionSwap, InvoiceSwap], // synchronize: true, }) -//npx typeorm migration:generate ./src/services/storage/migrations/invoice_swaps_fixes -d ./datasource.js \ No newline at end of file +//npx typeorm migration:generate ./src/services/storage/migrations/swap_timestamps -d ./datasource.js \ No newline at end of file diff --git a/proto/autogenerated/client.md b/proto/autogenerated/client.md index 75b44d4e..679e0d30 100644 --- a/proto/autogenerated/client.md +++ b/proto/autogenerated/client.md @@ -1383,17 +1383,18 @@ The nostr server will send back a message response, and inside the body there wi - __url__: _string_ ### InvoiceSwapOperation + - __completed_at_unix__: _number_ *this field is optional - __failure_reason__: _string_ *this field is optional - - __invoice_paid__: _string_ - __operation_payment__: _[UserOperation](#UserOperation)_ *this field is optional - - __swap_operation_id__: _string_ - - __tx_id__: _string_ + - __quote__: _[InvoiceSwapQuote](#InvoiceSwapQuote)_ ### InvoiceSwapQuote - __address__: _string_ - __chain_fee_sats__: _number_ + - __expires_at_block_height__: _number_ - __invoice__: _string_ - __invoice_amount_sats__: _number_ + - __paid_at_unix__: _number_ - __service_fee_sats__: _number_ - __service_url__: _string_ - __swap_fee_sats__: _number_ @@ -1408,7 +1409,7 @@ The nostr server will send back a message response, and inside the body there wi - __amount_sats__: _number_ ### InvoiceSwapsList - - __quotes__: ARRAY of: _[InvoiceSwapQuote](#InvoiceSwapQuote)_ + - __current_block_height__: _number_ - __swaps__: ARRAY of: _[InvoiceSwapOperation](#InvoiceSwapOperation)_ ### LatestBundleMetricReq diff --git a/proto/autogenerated/go/types.go b/proto/autogenerated/go/types.go index 2c79cb0a..a59adaaf 100644 --- a/proto/autogenerated/go/types.go +++ b/proto/autogenerated/go/types.go @@ -360,17 +360,18 @@ type HttpCreds struct { Url string `json:"url"` } type InvoiceSwapOperation struct { - Failure_reason string `json:"failure_reason"` - Invoice_paid string `json:"invoice_paid"` - Operation_payment *UserOperation `json:"operation_payment"` - Swap_operation_id string `json:"swap_operation_id"` - Tx_id string `json:"tx_id"` + Completed_at_unix int64 `json:"completed_at_unix"` + Failure_reason string `json:"failure_reason"` + Operation_payment *UserOperation `json:"operation_payment"` + Quote *InvoiceSwapQuote `json:"quote"` } type InvoiceSwapQuote struct { Address string `json:"address"` Chain_fee_sats int64 `json:"chain_fee_sats"` + Expires_at_block_height int64 `json:"expires_at_block_height"` Invoice string `json:"invoice"` Invoice_amount_sats int64 `json:"invoice_amount_sats"` + Paid_at_unix int64 `json:"paid_at_unix"` Service_fee_sats int64 `json:"service_fee_sats"` Service_url string `json:"service_url"` Swap_fee_sats int64 `json:"swap_fee_sats"` @@ -385,8 +386,8 @@ type InvoiceSwapRequest struct { Amount_sats int64 `json:"amount_sats"` } type InvoiceSwapsList struct { - Quotes []InvoiceSwapQuote `json:"quotes"` - Swaps []InvoiceSwapOperation `json:"swaps"` + Current_block_height int64 `json:"current_block_height"` + Swaps []InvoiceSwapOperation `json:"swaps"` } type LatestBundleMetricReq struct { Limit int64 `json:"limit"` diff --git a/proto/autogenerated/ts/types.ts b/proto/autogenerated/ts/types.ts index 17fec2b1..82f2c1fe 100644 --- a/proto/autogenerated/ts/types.ts +++ b/proto/autogenerated/ts/types.ts @@ -2123,43 +2123,39 @@ export const HttpCredsValidate = (o?: HttpCreds, opts: HttpCredsOptions = {}, pa } export type InvoiceSwapOperation = { + completed_at_unix?: number failure_reason?: string - invoice_paid: string operation_payment?: UserOperation - swap_operation_id: string - tx_id: string + quote: InvoiceSwapQuote } -export type InvoiceSwapOperationOptionalField = 'failure_reason' | 'operation_payment' -export const InvoiceSwapOperationOptionalFields: InvoiceSwapOperationOptionalField[] = ['failure_reason', 'operation_payment'] +export type InvoiceSwapOperationOptionalField = 'completed_at_unix' | 'failure_reason' | 'operation_payment' +export const InvoiceSwapOperationOptionalFields: InvoiceSwapOperationOptionalField[] = ['completed_at_unix', 'failure_reason', 'operation_payment'] export type InvoiceSwapOperationOptions = OptionsBaseMessage & { checkOptionalsAreSet?: InvoiceSwapOperationOptionalField[] + completed_at_unix_CustomCheck?: (v?: number) => boolean failure_reason_CustomCheck?: (v?: string) => boolean - invoice_paid_CustomCheck?: (v: string) => boolean operation_payment_Options?: UserOperationOptions - swap_operation_id_CustomCheck?: (v: string) => boolean - tx_id_CustomCheck?: (v: string) => boolean + quote_Options?: InvoiceSwapQuoteOptions } 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') if (typeof o !== 'object' || o === null) return new Error(path + ': object is not an instance of an object or is null') + if ((o.completed_at_unix || opts.allOptionalsAreSet || opts.checkOptionalsAreSet?.includes('completed_at_unix')) && typeof o.completed_at_unix !== 'number') return new Error(`${path}.completed_at_unix: is not a number`) + if (opts.completed_at_unix_CustomCheck && !opts.completed_at_unix_CustomCheck(o.completed_at_unix)) return new Error(`${path}.completed_at_unix: custom check failed`) + if ((o.failure_reason || opts.allOptionalsAreSet || opts.checkOptionalsAreSet?.includes('failure_reason')) && typeof o.failure_reason !== 'string') return new Error(`${path}.failure_reason: is not a string`) if (opts.failure_reason_CustomCheck && !opts.failure_reason_CustomCheck(o.failure_reason)) return new Error(`${path}.failure_reason: custom check failed`) - if (typeof o.invoice_paid !== 'string') return new Error(`${path}.invoice_paid: is not a string`) - if (opts.invoice_paid_CustomCheck && !opts.invoice_paid_CustomCheck(o.invoice_paid)) return new Error(`${path}.invoice_paid: custom check failed`) - if (typeof o.operation_payment === 'object' || opts.allOptionalsAreSet || opts.checkOptionalsAreSet?.includes('operation_payment')) { const operation_paymentErr = UserOperationValidate(o.operation_payment, opts.operation_payment_Options, `${path}.operation_payment`) if (operation_paymentErr !== null) return operation_paymentErr } - if (typeof o.swap_operation_id !== 'string') return new Error(`${path}.swap_operation_id: is not a string`) - if (opts.swap_operation_id_CustomCheck && !opts.swap_operation_id_CustomCheck(o.swap_operation_id)) return new Error(`${path}.swap_operation_id: custom check failed`) - - if (typeof o.tx_id !== 'string') return new Error(`${path}.tx_id: is not a string`) - if (opts.tx_id_CustomCheck && !opts.tx_id_CustomCheck(o.tx_id)) return new Error(`${path}.tx_id: custom check failed`) + const quoteErr = InvoiceSwapQuoteValidate(o.quote, opts.quote_Options, `${path}.quote`) + if (quoteErr !== null) return quoteErr + return null } @@ -2167,8 +2163,10 @@ export const InvoiceSwapOperationValidate = (o?: InvoiceSwapOperation, opts: Inv export type InvoiceSwapQuote = { address: string chain_fee_sats: number + expires_at_block_height: number invoice: string invoice_amount_sats: number + paid_at_unix: number service_fee_sats: number service_url: string swap_fee_sats: number @@ -2181,8 +2179,10 @@ export type InvoiceSwapQuoteOptions = OptionsBaseMessage & { checkOptionalsAreSet?: [] address_CustomCheck?: (v: string) => boolean chain_fee_sats_CustomCheck?: (v: number) => boolean + expires_at_block_height_CustomCheck?: (v: number) => boolean invoice_CustomCheck?: (v: string) => boolean invoice_amount_sats_CustomCheck?: (v: number) => boolean + paid_at_unix_CustomCheck?: (v: number) => boolean service_fee_sats_CustomCheck?: (v: number) => boolean service_url_CustomCheck?: (v: string) => boolean swap_fee_sats_CustomCheck?: (v: number) => boolean @@ -2200,12 +2200,18 @@ export const InvoiceSwapQuoteValidate = (o?: InvoiceSwapQuote, opts: InvoiceSwap if (typeof o.chain_fee_sats !== 'number') return new Error(`${path}.chain_fee_sats: is not a number`) if (opts.chain_fee_sats_CustomCheck && !opts.chain_fee_sats_CustomCheck(o.chain_fee_sats)) return new Error(`${path}.chain_fee_sats: custom check failed`) + if (typeof o.expires_at_block_height !== 'number') return new Error(`${path}.expires_at_block_height: is not a number`) + if (opts.expires_at_block_height_CustomCheck && !opts.expires_at_block_height_CustomCheck(o.expires_at_block_height)) return new Error(`${path}.expires_at_block_height: custom check failed`) + if (typeof o.invoice !== 'string') return new Error(`${path}.invoice: is not a string`) if (opts.invoice_CustomCheck && !opts.invoice_CustomCheck(o.invoice)) return new Error(`${path}.invoice: custom check failed`) if (typeof o.invoice_amount_sats !== 'number') return new Error(`${path}.invoice_amount_sats: is not a number`) if (opts.invoice_amount_sats_CustomCheck && !opts.invoice_amount_sats_CustomCheck(o.invoice_amount_sats)) return new Error(`${path}.invoice_amount_sats: custom check failed`) + if (typeof o.paid_at_unix !== 'number') return new Error(`${path}.paid_at_unix: is not a number`) + if (opts.paid_at_unix_CustomCheck && !opts.paid_at_unix_CustomCheck(o.paid_at_unix)) return new Error(`${path}.paid_at_unix: custom check failed`) + if (typeof o.service_fee_sats !== 'number') return new Error(`${path}.service_fee_sats: is not a number`) if (opts.service_fee_sats_CustomCheck && !opts.service_fee_sats_CustomCheck(o.service_fee_sats)) return new Error(`${path}.service_fee_sats: custom check failed`) @@ -2269,14 +2275,13 @@ export const InvoiceSwapRequestValidate = (o?: InvoiceSwapRequest, opts: Invoice } export type InvoiceSwapsList = { - quotes: InvoiceSwapQuote[] + current_block_height: number swaps: InvoiceSwapOperation[] } export const InvoiceSwapsListOptionalFields: [] = [] export type InvoiceSwapsListOptions = OptionsBaseMessage & { checkOptionalsAreSet?: [] - quotes_ItemOptions?: InvoiceSwapQuoteOptions - quotes_CustomCheck?: (v: InvoiceSwapQuote[]) => boolean + current_block_height_CustomCheck?: (v: number) => boolean swaps_ItemOptions?: InvoiceSwapOperationOptions swaps_CustomCheck?: (v: InvoiceSwapOperation[]) => boolean } @@ -2284,12 +2289,8 @@ export const InvoiceSwapsListValidate = (o?: InvoiceSwapsList, opts: InvoiceSwap if (opts.checkOptionalsAreSet && opts.allOptionalsAreSet) return new Error(path + ': only one of checkOptionalsAreSet or allOptionalNonDefault can be set for each message') if (typeof o !== 'object' || o === null) return new Error(path + ': object is not an instance of an object or is null') - if (!Array.isArray(o.quotes)) return new Error(`${path}.quotes: is not an array`) - for (let index = 0; index < o.quotes.length; index++) { - const quotesErr = InvoiceSwapQuoteValidate(o.quotes[index], opts.quotes_ItemOptions, `${path}.quotes[${index}]`) - if (quotesErr !== null) return quotesErr - } - if (opts.quotes_CustomCheck && !opts.quotes_CustomCheck(o.quotes)) return new Error(`${path}.quotes: custom check failed`) + if (typeof o.current_block_height !== 'number') return new Error(`${path}.current_block_height: is not a number`) + if (opts.current_block_height_CustomCheck && !opts.current_block_height_CustomCheck(o.current_block_height)) return new Error(`${path}.current_block_height: custom check failed`) if (!Array.isArray(o.swaps)) return new Error(`${path}.swaps: is not an array`) for (let index = 0; index < o.swaps.length; index++) { diff --git a/proto/service/structs.proto b/proto/service/structs.proto index 2ce6adc3..07b930da 100644 --- a/proto/service/structs.proto +++ b/proto/service/structs.proto @@ -848,6 +848,8 @@ message InvoiceSwapQuote { string service_url = 8; int64 swap_fee_sats = 9; string tx_id = 10; + int64 paid_at_unix = 11; + int64 expires_at_block_height = 12; } message InvoiceSwapQuoteList { @@ -855,16 +857,15 @@ message InvoiceSwapQuoteList { } message InvoiceSwapOperation { - string swap_operation_id = 1; + InvoiceSwapQuote quote = 1; optional UserOperation operation_payment = 2; optional string failure_reason = 3; - string invoice_paid = 4; - string tx_id = 5; + optional int64 completed_at_unix = 6; } message InvoiceSwapsList { repeated InvoiceSwapOperation swaps = 1; - repeated InvoiceSwapQuote quotes = 2; + int64 current_block_height = 3; } message RefundAdminInvoiceSwapRequest { diff --git a/src/services/lnd/swaps/swaps.ts b/src/services/lnd/swaps/swaps.ts index dc57309a..3a21d2f8 100644 --- a/src/services/lnd/swaps/swaps.ts +++ b/src/services/lnd/swaps/swaps.ts @@ -71,32 +71,37 @@ export class Swaps { return success } + private mapInvoiceSwapQuote = (s: InvoiceSwap): Types.InvoiceSwapQuote => { + return { + swap_operation_id: s.swap_operation_id, + invoice: s.invoice, + invoice_amount_sats: s.invoice_amount, + address: s.address, + transaction_amount_sats: s.transaction_amount, + chain_fee_sats: s.chain_fee_sats, + service_fee_sats: 0, + service_url: s.service_url, + swap_fee_sats: s.swap_fee_sats, + tx_id: s.tx_id, + paid_at_unix: s.paid_at_unix, + expires_at_block_height: s.timeout_block_height, + } + } + ListInvoiceSwaps = async (appUserId: string): Promise => { + const info = await this.lnd.GetInfo() + const currentBlockHeight = info.blockHeight const completedSwaps = await this.storage.paymentStorage.ListCompletedInvoiceSwaps(appUserId) const pendingSwaps = await this.storage.paymentStorage.ListPendingInvoiceSwaps(appUserId) + const quotes: Types.InvoiceSwapOperation[] = pendingSwaps.map(s => ({ quote: this.mapInvoiceSwapQuote(s) })) + const operations: Types.InvoiceSwapOperation[] = completedSwaps.map(s => ({ + quote: this.mapInvoiceSwapQuote(s), + failure_reason: s.failure_reason, + completed_at_unix: s.completed_at_unix || 1, + })) return { - swaps: completedSwaps.map(s => { - return { - invoice_paid: s.invoice, - swap_operation_id: s.swap_operation_id, - failure_reason: s.failure_reason, - tx_id: s.tx_id, - } - }), - quotes: pendingSwaps.map(s => { - return { - swap_operation_id: s.swap_operation_id, - invoice: s.invoice, - invoice_amount_sats: s.invoice_amount, - address: s.address, - transaction_amount_sats: s.transaction_amount, - chain_fee_sats: s.chain_fee_sats, - service_fee_sats: 0, - service_url: s.service_url, - swap_fee_sats: s.swap_fee_sats, - tx_id: s.tx_id, - } - }) + current_block_height: currentBlockHeight, + swaps: operations.concat(quotes), } } @@ -261,6 +266,8 @@ export class Swaps { service_url: swapper.getHttpUrl(), swap_fee_sats: fee, tx_id: newSwap.tx_id, + paid_at_unix: newSwap.paid_at_unix, + expires_at_block_height: newSwap.timeout_block_height, } } diff --git a/src/services/main/adminManager.ts b/src/services/main/adminManager.ts index 1581c245..4449e9ca 100644 --- a/src/services/main/adminManager.ts +++ b/src/services/main/adminManager.ts @@ -280,14 +280,16 @@ export class AdminManager { // Fetch the full transaction hex for potential refunds let lockupTxHex: string | undefined + let chainFeeSats = 0 try { const txDetails = await this.lnd.GetTx(tx.txid) + chainFeeSats = Number(txDetails.totalFees) lockupTxHex = txDetails.rawTxHex } catch (err: any) { this.log("Warning: Could not fetch transaction hex for refund purposes:", err.message) } - await this.storage.paymentStorage.SetInvoiceSwapTxId(req.swap_operation_id, tx.txid, lockupTxHex) + await this.storage.paymentStorage.SetInvoiceSwapTxId(req.swap_operation_id, tx.txid, chainFeeSats, lockupTxHex) this.log("saved admin swap txid", { swapOpId: req.swap_operation_id, txId: tx.txid }) res(tx.txid) return { txId: tx.txid } diff --git a/src/services/nostr/index.ts b/src/services/nostr/index.ts index 5b2f0ddc..ffa636b6 100644 --- a/src/services/nostr/index.ts +++ b/src/services/nostr/index.ts @@ -16,7 +16,7 @@ export default class NostrSubprocess { private eventCallback: EventCallback private beaconCallback: BeaconCallback private isShuttingDown = false - + constructor(settings: NostrSettings, utils: Utils, eventCallback: EventCallback, beaconCallback: BeaconCallback) { this.utils = utils this.settings = settings @@ -36,9 +36,9 @@ export default class NostrSubprocess { private startSubProcess() { this.cleanupProcess() - + this.childProcess = fork("./build/src/services/nostr/handler") - + this.childProcess.on("error", (error) => { this.log(ERROR, "nostr subprocess error", error) }) @@ -48,20 +48,20 @@ export default class NostrSubprocess { this.log("nostr subprocess stopped") return } - + if (code === 0) { this.log("nostr subprocess exited cleanly") return } - + this.log(ERROR, `nostr subprocess exited with code ${code} and signal ${signal}`) - + const now = Date.now() if (now - this.latestRestart < 5000) { this.log(ERROR, "nostr subprocess exited too quickly, not restarting") throw new Error("nostr subprocess crashed repeatedly") } - + this.log("restarting nostr subprocess...") this.latestRestart = now setTimeout(() => this.startSubProcess(), 100) @@ -113,7 +113,7 @@ export default class NostrSubprocess { Send(initiator: SendInitiator, data: SendData, relays?: string[]) { this.sendToChildProcess({ type: 'send', data, initiator, relays }) } - + Stop() { this.isShuttingDown = true this.cleanupProcess() diff --git a/src/services/storage/entity/InvoiceSwap.ts b/src/services/storage/entity/InvoiceSwap.ts index f435edab..746756b8 100644 --- a/src/services/storage/entity/InvoiceSwap.ts +++ b/src/services/storage/entity/InvoiceSwap.ts @@ -62,6 +62,12 @@ export class InvoiceSwap { @Column({ default: false }) used: boolean + @Column({ default: 0 }) + completed_at_unix: number + + @Column({ default: 0 }) + paid_at_unix: number + @Column({ default: "" }) preimage: string diff --git a/src/services/storage/migrations/1771347307798-swap_timestamps.ts b/src/services/storage/migrations/1771347307798-swap_timestamps.ts new file mode 100644 index 00000000..ce5c0b39 --- /dev/null +++ b/src/services/storage/migrations/1771347307798-swap_timestamps.ts @@ -0,0 +1,20 @@ +import { MigrationInterface, QueryRunner } from "typeorm"; + +export class SwapTimestamps1771347307798 implements MigrationInterface { + name = 'SwapTimestamps1771347307798' + + 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))`); + 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") 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" 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 (''))`); + 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") 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" 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 0b08799a..eb5d7c19 100644 --- a/src/services/storage/migrations/runner.ts +++ b/src/services/storage/migrations/runner.ts @@ -34,13 +34,15 @@ import { TrackedProviderHeight1766504040000 } from './1766504040000-tracked_prov import { SwapsServiceUrl1768413055036 } from './1768413055036-swaps_service_url.js' import { InvoiceSwaps1769529793283 } from './1769529793283-invoice_swaps.js' import { InvoiceSwapsFixes1769805357459 } from './1769805357459-invoice_swaps_fixes.js' +import { SwapTimestamps1771347307798 } from './1771347307798-swap_timestamps.js' export const allMigrations = [Initial1703170309875, LspOrder1718387847693, LiquidityProvider1719335699480, LndNodeInfo1720187506189, TrackedProvider1720814323679, CreateInviteTokenTable1721751414878, PaymentIndex1721760297610, DebitAccess1726496225078, DebitAccessFixes1726685229264, DebitToPub1727105758354, UserCbUrl1727112281043, UserOffer1733502626042, ManagementGrant1751307732346, ManagementGrantBanned1751989251513, InvoiceCallbackUrls1752425992291, OldSomethingLeftover1753106599604, UserReceivingInvoiceIdx1753109184611, AppUserDevice1753285173175, UserAccess1759426050669, AddBlindToUserOffer1760000000000, ApplicationAvatarUrl1761000001000, AdminSettings1761683639419, TxSwap1762890527098, - TxSwapAddress1764779178945, ClinkRequester1765497600000, TrackedProviderHeight1766504040000, SwapsServiceUrl1768413055036, InvoiceSwaps1769529793283, InvoiceSwapsFixes1769805357459] + TxSwapAddress1764779178945, ClinkRequester1765497600000, TrackedProviderHeight1766504040000, SwapsServiceUrl1768413055036, + InvoiceSwaps1769529793283, InvoiceSwapsFixes1769805357459, SwapTimestamps1771347307798] export const allMetricsMigrations = [LndMetrics1703170330183, ChannelRouting1709316653538, HtlcCount1724266887195, BalanceEvents1724860966825, RootOps1732566440447, RootOpsTime1745428134124, ChannelEvents1750777346411] /* export const TypeOrmMigrationRunner = async (log: PubLogger, storageManager: Storage, settings: DbSettings, arg: string | undefined): Promise => { diff --git a/src/services/storage/paymentStorage.ts b/src/services/storage/paymentStorage.ts index 5726c5cf..785f9909 100644 --- a/src/services/storage/paymentStorage.ts +++ b/src/services/storage/paymentStorage.ts @@ -530,8 +530,10 @@ export default class { } async FinalizeInvoiceSwap(swapOperationId: string, txId?: string) { + const now = Math.floor(Date.now() / 1000) return this.dbs.Update('InvoiceSwap', { swap_operation_id: swapOperationId }, { used: true, + completed_at_unix: now, }, txId) } @@ -539,9 +541,12 @@ export default class { return this.dbs.Update('InvoiceSwap', { swap_operation_id: swapOperationId }, update, txId) } - async SetInvoiceSwapTxId(swapOperationId: string, chainTxId: string, lockupTxHex?: string, txId?: string) { + async SetInvoiceSwapTxId(swapOperationId: string, chainTxId: string, chainFeeSats: number, lockupTxHex?: string, txId?: string) { + const now = Math.floor(Date.now() / 1000) const update: Partial = { tx_id: chainTxId, + paid_at_unix: now, + chain_fee_sats: chainFeeSats, } if (lockupTxHex) { update.lockup_tx_hex = lockupTxHex @@ -550,9 +555,11 @@ export default class { } async FailInvoiceSwap(swapOperationId: string, failureReason: string, txId?: string) { + const now = Math.floor(Date.now() / 1000) return this.dbs.Update('InvoiceSwap', { swap_operation_id: swapOperationId }, { used: true, failure_reason: failureReason, + completed_at_unix: now, }, txId) }