Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions src/Api/pub/autogenerated/ts/nostr_client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -137,6 +137,18 @@ export default (params: NostrClientParams, send: (to: string, message: NostrRequ
}
return { status: 'ERROR', reason: 'invalid response' }
},
BumpTx: async (request: Types.BumpTx): Promise<ResultError | ({ status: 'OK' })> => {
const auth = await params.retrieveNostrAdminAuth()
if (auth === null) throw new Error('retrieveNostrAdminAuth() returned null')
const nostrRequest: NostrRequest = {}
nostrRequest.body = request
const data = await send(params.pubDestination, { rpcName: 'BumpTx', authIdentifier: auth, ...nostrRequest })
if (data.status === 'ERROR' && typeof data.reason === 'string') return data
if (data.status === 'OK') {
return data
}
return { status: 'ERROR', reason: 'invalid response' }
},
CloseChannel: async (request: Types.CloseChannelRequest): Promise<ResultError | ({ status: 'OK' } & Types.CloseChannelResponse)> => {
const auth = await params.retrieveNostrAdminAuth()
if (auth === null) throw new Error('retrieveNostrAdminAuth() returned null')
Expand Down
16 changes: 16 additions & 0 deletions src/Api/pub/autogenerated/ts/nostr_transport.ts
Original file line number Diff line number Diff line change
Expand Up @@ -575,6 +575,22 @@ export default (methods: Types.ServerMethods, opts: NostrOptions) => {
opts.metricsCallback([{ ...info, ...stats, ...ctx }, ...callsMetrics])
} catch (ex) { const e = ex as any; logErrorAndReturnResponse(e, e.message || e, res, logger, { ...info, ...stats, ...authCtx }, opts.metricsCallback); if (opts.throwErrors) throw e }
break
case 'BumpTx':
try {
if (!methods.BumpTx) throw new Error('method: BumpTx is not implemented')
const authContext = await opts.NostrAdminAuthGuard(req.appId, req.authIdentifier)
stats.guard = process.hrtime.bigint()
authCtx = authContext
const request = req.body
const error = Types.BumpTxValidate(request)
stats.validate = process.hrtime.bigint()
if (error !== null) return logErrorAndReturnResponse(error, 'invalid request body', res, logger, { ...info, ...stats, ...authCtx }, opts.metricsCallback)
await methods.BumpTx({ rpcName: 'BumpTx', ctx: authContext, req: request })
stats.handle = process.hrtime.bigint()
res({ status: 'OK' })
opts.metricsCallback([{ ...info, ...stats, ...authContext }])
} catch (ex) { const e = ex as any; logErrorAndReturnResponse(e, e.message || e, res, logger, { ...info, ...stats, ...authCtx }, opts.metricsCallback); if (opts.throwErrors) throw e }
break
case 'CloseChannel':
try {
if (!methods.CloseChannel) throw new Error('method: CloseChannel is not implemented')
Expand Down
36 changes: 34 additions & 2 deletions src/Api/pub/autogenerated/ts/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,8 @@ export type RequestMetric = AuthContext & RequestInfo & RequestStats & { error?:
export type AdminContext = {
admin_id: string
}
export type AdminMethodInputs = AddApp_Input | AddPeer_Input | AuthApp_Input | BanUser_Input | CloseChannel_Input | CreateOneTimeInviteLink_Input | GetAdminInvoiceSwapQuotes_Input | GetAdminTransactionSwapQuotes_Input | GetAssetsAndLiabilities_Input | GetInviteLinkState_Input | GetSeed_Input | ListAdminInvoiceSwaps_Input | ListAdminTxSwaps_Input | ListChannels_Input | LndGetInfo_Input | OpenChannel_Input | PayAdminInvoiceSwap_Input | PayAdminTransactionSwap_Input | RefundAdminInvoiceSwap_Input | UpdateChannelPolicy_Input
export type AdminMethodOutputs = AddApp_Output | AddPeer_Output | AuthApp_Output | BanUser_Output | CloseChannel_Output | CreateOneTimeInviteLink_Output | GetAdminInvoiceSwapQuotes_Output | GetAdminTransactionSwapQuotes_Output | GetAssetsAndLiabilities_Output | GetInviteLinkState_Output | GetSeed_Output | ListAdminInvoiceSwaps_Output | ListAdminTxSwaps_Output | ListChannels_Output | LndGetInfo_Output | OpenChannel_Output | PayAdminInvoiceSwap_Output | PayAdminTransactionSwap_Output | RefundAdminInvoiceSwap_Output | UpdateChannelPolicy_Output
export type AdminMethodInputs = AddApp_Input | AddPeer_Input | AuthApp_Input | BanUser_Input | BumpTx_Input | CloseChannel_Input | CreateOneTimeInviteLink_Input | GetAdminInvoiceSwapQuotes_Input | GetAdminTransactionSwapQuotes_Input | GetAssetsAndLiabilities_Input | GetInviteLinkState_Input | GetSeed_Input | ListAdminInvoiceSwaps_Input | ListAdminTxSwaps_Input | ListChannels_Input | LndGetInfo_Input | OpenChannel_Input | PayAdminInvoiceSwap_Input | PayAdminTransactionSwap_Input | RefundAdminInvoiceSwap_Input | UpdateChannelPolicy_Input
export type AdminMethodOutputs = AddApp_Output | AddPeer_Output | AuthApp_Output | BanUser_Output | BumpTx_Output | CloseChannel_Output | CreateOneTimeInviteLink_Output | GetAdminInvoiceSwapQuotes_Output | GetAdminTransactionSwapQuotes_Output | GetAssetsAndLiabilities_Output | GetInviteLinkState_Output | GetSeed_Output | ListAdminInvoiceSwaps_Output | ListAdminTxSwaps_Output | ListChannels_Output | LndGetInfo_Output | OpenChannel_Output | PayAdminInvoiceSwap_Output | PayAdminTransactionSwap_Output | RefundAdminInvoiceSwap_Output | UpdateChannelPolicy_Output
export type AppContext = {
app_id: string
}
Expand Down Expand Up @@ -75,6 +75,9 @@ export type BanUser_Output = ResultError | ({ status: 'OK' } & BanUserResponse)
export type BatchUser_Input = UserMethodInputs
export type BatchUser_Output = UserMethodOutputs

export type BumpTx_Input = { rpcName: 'BumpTx', req: BumpTx }
export type BumpTx_Output = ResultError | { status: 'OK' }

export type CloseChannel_Input = { rpcName: 'CloseChannel', req: CloseChannelRequest }
export type CloseChannel_Output = ResultError | ({ status: 'OK' } & CloseChannelResponse)

Expand Down Expand Up @@ -364,6 +367,7 @@ export type ServerMethods = {
AuthorizeManage?: (req: AuthorizeManage_Input & { ctx: UserContext }) => Promise<ManageAuthorization>
BanDebit?: (req: BanDebit_Input & { ctx: UserContext }) => Promise<void>
BanUser?: (req: BanUser_Input & { ctx: AdminContext }) => Promise<BanUserResponse>
BumpTx?: (req: BumpTx_Input & { ctx: AdminContext }) => Promise<void>
CloseChannel?: (req: CloseChannel_Input & { ctx: AdminContext }) => Promise<CloseChannelResponse>
CreateOneTimeInviteLink?: (req: CreateOneTimeInviteLink_Input & { ctx: AdminContext }) => Promise<CreateOneTimeInviteLinkResponse>
DecodeInvoice?: (req: DecodeInvoice_Input & { ctx: UserContext }) => Promise<DecodeInvoiceResponse>
Expand Down Expand Up @@ -1209,6 +1213,34 @@ export const BeaconDataValidate = (o?: BeaconData, opts: BeaconDataOptions = {},
return null
}

export type BumpTx = {
output_index: number
sat_per_vbyte: number
txid: string
}
export const BumpTxOptionalFields: [] = []
export type BumpTxOptions = OptionsBaseMessage & {
checkOptionalsAreSet?: []
output_index_CustomCheck?: (v: number) => boolean
sat_per_vbyte_CustomCheck?: (v: number) => boolean
txid_CustomCheck?: (v: string) => boolean
}
export const BumpTxValidate = (o?: BumpTx, opts: BumpTxOptions = {}, path: string = 'BumpTx::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 (typeof o.output_index !== 'number') return new Error(`${path}.output_index: is not a number`)
if (opts.output_index_CustomCheck && !opts.output_index_CustomCheck(o.output_index)) return new Error(`${path}.output_index: custom check failed`)

if (typeof o.sat_per_vbyte !== 'number') return new Error(`${path}.sat_per_vbyte: is not a number`)
if (opts.sat_per_vbyte_CustomCheck && !opts.sat_per_vbyte_CustomCheck(o.sat_per_vbyte)) return new Error(`${path}.sat_per_vbyte: custom check failed`)

if (typeof o.txid !== 'string') return new Error(`${path}.txid: is not a string`)
if (opts.txid_CustomCheck && !opts.txid_CustomCheck(o.txid)) return new Error(`${path}.txid: custom check failed`)

return null
}

export type BundleData = {
available_chunks: number[]
base_64_data: string[]
Expand Down
103 changes: 92 additions & 11 deletions src/Pages/Metrics/adminSwaps/AdminSwaps.tsx
Original file line number Diff line number Diff line change
@@ -1,19 +1,98 @@
import { useEffect, useMemo, useState } from "react"
import { Period } from "@/Components/Dropdowns/LVDropdown"
import { useMemo, useState } from "react"
import { toast } from "react-toastify";
import * as Types from '../../../Api/pub/autogenerated/ts/types';
import { IonButton, IonContent, IonItem, IonPage, IonHeader, useIonLoading, useIonRouter, IonRow, IonText, IonInput, IonSegment, IonLabel, IonSegmentButton } from "@ionic/react";
import { flashOutline, linkOutline, personOutline } from "ionicons/icons";
import { IonButton, IonContent, IonCard, IonCardContent, IonCardHeader, IonCardTitle, IonIcon, IonInput, IonItem, IonLabel, IonPage, IonHeader, IonSegment, IonSegmentButton, useIonLoading } from "@ionic/react";
import { flashOutline } from "ionicons/icons";
import MetricsSubPageToolbar from "@/Layout2/Metrics/MetricsSubPageToolbar";
import { useAppSelector } from "@/State/store/hooks";
import { selectAdminNprofileViews } from "@/State/scoped/backups/sources/selectors";
import { NprofileView, selectAdminNprofileViews } from "@/State/scoped/backups/sources/selectors";
import { selectSelectedMetricsAdminSourceId } from "@/State/runtime/slice";
import { TxSwapsList, TransactionSwapQuote } from "../../../Api/pub/autogenerated/ts/types";
import { fetcher, FetcherFuncs } from "../fetcher";
import AmountInput from "@/Components/AmountInput";
import { fetcher } from "../fetcher";
import TxSwaps from "./TxSwaps";
import SubmarineSwaps from "./SubmarineSwaps";

function BumpTxSection({ adminSource }: { adminSource: NprofileView | undefined }) {
const [txid, setTxid] = useState("");
const [outputIndex, setOutputIndex] = useState("");
const [satPerVbyte, setSatPerVbyte] = useState("");
const [presentLoading, dismissLoading] = useIonLoading();

const bumpTx = async () => {
if (!adminSource) return;
const outIdx = parseInt(outputIndex, 10);
const sats = parseFloat(satPerVbyte);
if (!txid.trim() || isNaN(outIdx) || outIdx < 0 || isNaN(sats) || sats <= 0) {
toast.error("Enter valid txid, output index (≥0), and sat/vB (>0)");
return;
}
const req: Types.BumpTx = { txid: txid.trim(), output_index: outIdx, sat_per_vbyte: sats };
const res = await fetcher(
{ pubkey: adminSource.lpk, relays: adminSource.relays },
adminSource.keys,
{
onStart: async () => { await dismissLoading(); await presentLoading("Bumping tx..."); },
onEnd: async () => { await dismissLoading(); },
onFail: (err) => { toast.error(err); },
},
[(client) => client.BumpTx(req)]
);
if (res) {
toast.success("Tx bump requested.");
setTxid("");
setOutputIndex("");
setSatPerVbyte("");
}
};

return (
<IonContent className="ion-padding ion-content-no-footer">
<IonCard>
<IonCardHeader>
<IonCardTitle>Bump transaction fee (RBF)</IonCardTitle>
</IonCardHeader>
<IonCardContent>
<IonItem lines="none">
<IonInput
label="Txid"
labelPlacement="stacked"
value={txid}
onIonInput={(e) => setTxid(e.detail.value ?? "")}
placeholder="hex txid"
className="ion-margin-bottom"
/>
</IonItem>
<IonItem lines="none">
<IonInput
label="Output index"
labelPlacement="stacked"
type="number"
min={0}
value={outputIndex}
onIonInput={(e) => setOutputIndex(e.detail.value ?? "")}
placeholder="0"
/>
</IonItem>
<IonItem lines="none">
<IonInput
label="Sat per vB"
labelPlacement="stacked"
type="number"
min={0}
step="any"
value={satPerVbyte}
onIonInput={(e) => setSatPerVbyte(e.detail.value ?? "")}
placeholder="e.g. 10"
/>
</IonItem>
<IonButton expand="block" onClick={bumpTx} disabled={!adminSource || !txid.trim() || outputIndex === "" || satPerVbyte === ""}>
<IonIcon icon={flashOutline} slot="start" /> Bump Tx
</IonButton>
</IonCardContent>
</IonCard>
</IonContent>
);
}

export default function AdminSwaps() {
const [selectedView, setSelectedView] = useState("reverse");
const admins = useAppSelector(selectAdminNprofileViews);
Expand All @@ -28,18 +107,20 @@ export default function AdminSwaps() {
<IonHeader className="ion-no-border">
<MetricsSubPageToolbar title="Admin Swaps" />
</IonHeader>
<IonSegment value="rev" onIonChange={(e) => {
setSelectedView(e.detail.value?.toString() || "");
}}>
<IonSegment value={selectedView} onIonChange={(e) => setSelectedView(e.detail.value?.toString() ?? "reverse")}>
<IonSegmentButton value="reverse">
<IonLabel>Reverse Swaps</IonLabel>
</IonSegmentButton>
<IonSegmentButton value="submarine">
<IonLabel>Submarine Swaps</IonLabel>
</IonSegmentButton>
<IonSegmentButton value="bump">
<IonLabel>Bump Tx</IonLabel>
</IonSegmentButton>
</IonSegment>
{selectedView === "reverse" && <TxSwaps adminSource={adminSource} />}
{selectedView === "submarine" && <SubmarineSwaps adminSource={adminSource} />}
{selectedView === "bump" && <BumpTxSection adminSource={adminSource} />}
</IonPage>;

}