diff --git a/README.md b/README.md
index d109c6cf..27dd7fac 100644
--- a/README.md
+++ b/README.md
@@ -565,6 +565,7 @@ Internally Alby Hub uses a basic implementation of the pubsub messaging pattern
- `nwc_alby_account_connected` - user connects alby account for first time
- `nwc_swap_succeeded` - successfully made a boltz swap
- `nwc_rebalance_succeeded` - successfully rebalanced channels
+ - `nwc_payment_forwarded` - successfully forwarded a payment and earned routing fees
### NIP-47 Handlers
diff --git a/api/api.go b/api/api.go
index 32d08690..5b2f9e76 100644
--- a/api/api.go
+++ b/api/api.go
@@ -554,6 +554,7 @@ func (api *api) ListChannels(ctx context.Context) ([]Channel, error) {
Confirmations: channel.Confirmations,
ConfirmationsRequired: channel.ConfirmationsRequired,
ForwardingFeeBaseMsat: channel.ForwardingFeeBaseMsat,
+ ForwardingFeeProportionalMillionths: channel.ForwardingFeeProportionalMillionths,
UnspendablePunishmentReserve: channel.UnspendablePunishmentReserve,
CounterpartyUnspendablePunishmentReserve: channel.CounterpartyUnspendablePunishmentReserve,
Error: channel.Error,
@@ -1582,3 +1583,27 @@ func (api *api) parseExpiresAt(expiresAtString string) (*time.Time, error) {
}
return expiresAt, nil
}
+
+func (api *api) GetForwards() (*GetForwardsResponse, error) {
+ var forwards []db.Forward
+ err := api.db.Find(&forwards).Error
+ if err != nil {
+ return nil, err
+ }
+
+ var totalOutboundAmount uint64
+ var totalFeeEarned uint64
+
+ for _, forward := range forwards {
+ totalOutboundAmount += forward.OutboundAmountForwardedMsat
+ totalFeeEarned += forward.TotalFeeEarnedMsat
+ }
+
+ numForwards := len(forwards)
+
+ return &GetForwardsResponse{
+ OutboundAmountForwardedMsat: totalOutboundAmount,
+ TotalFeeEarnedMsat: totalFeeEarned,
+ NumForwards: uint64(numForwards),
+ }, nil
+}
diff --git a/api/models.go b/api/models.go
index ff0633d6..fa7a7259 100644
--- a/api/models.go
+++ b/api/models.go
@@ -79,6 +79,7 @@ type API interface {
GetCustomNodeCommands() (*CustomNodeCommandsResponse, error)
ExecuteCustomNodeCommand(ctx context.Context, command string) (interface{}, error)
SendEvent(event string)
+ GetForwards() (*GetForwardsResponse, error)
}
type App struct {
@@ -489,6 +490,7 @@ type Channel struct {
Confirmations *uint32 `json:"confirmations"`
ConfirmationsRequired *uint32 `json:"confirmationsRequired"`
ForwardingFeeBaseMsat uint32 `json:"forwardingFeeBaseMsat"`
+ ForwardingFeeProportionalMillionths uint32 `json:"forwardingFeeProportionalMillionths"`
UnspendablePunishmentReserve uint64 `json:"unspendablePunishmentReserve"`
CounterpartyUnspendablePunishmentReserve uint64 `json:"counterpartyUnspendablePunishmentReserve"`
Error *string `json:"error"`
@@ -544,3 +546,9 @@ type CustomNodeCommandsResponse struct {
type ExecuteCustomNodeCommandRequest struct {
Command string `json:"command"`
}
+
+type GetForwardsResponse struct {
+ OutboundAmountForwardedMsat uint64 `json:"outboundAmountForwardedMsat"`
+ TotalFeeEarnedMsat uint64 `json:"totalFeeEarnedMsat"`
+ NumForwards uint64 `json:"numForwards"`
+}
diff --git a/cmd/db_migrate/main.go b/cmd/db_migrate/main.go
index 43aa9ae5..c10f4a3e 100644
--- a/cmd/db_migrate/main.go
+++ b/cmd/db_migrate/main.go
@@ -23,6 +23,7 @@ var expectedTables = []string{
"swaps",
"user_configs",
"migrations",
+ "forwards",
}
func main() {
diff --git a/db/migrations/202508192137_forwards.go b/db/migrations/202508192137_forwards.go
new file mode 100644
index 00000000..174fe783
--- /dev/null
+++ b/db/migrations/202508192137_forwards.go
@@ -0,0 +1,36 @@
+package migrations
+
+import (
+ _ "embed"
+ "text/template"
+
+ "github.com/go-gormigrate/gormigrate/v2"
+ "gorm.io/gorm"
+)
+
+const forwardsMigration = `
+CREATE TABLE forwards(
+ id {{ .AutoincrementPrimaryKey }},
+ outbound_amount_forwarded_msat bigint,
+ total_fee_earned_msat bigint,
+ created_at {{ .Timestamp }},
+ updated_at {{ .Timestamp }}
+);
+`
+
+var forwardsMigrationTmpl = template.Must(template.New("forwardsMigration").Parse(forwardsMigration))
+
+var _202508192137_forwards = &gormigrate.Migration{
+ ID: "202508192137_forwards",
+ Migrate: func(tx *gorm.DB) error {
+
+ if err := exec(tx, forwardsMigrationTmpl); err != nil {
+ return err
+ }
+
+ return nil
+ },
+ Rollback: func(tx *gorm.DB) error {
+ return nil
+ },
+}
diff --git a/db/migrations/migrate.go b/db/migrations/migrate.go
index 7f304cc4..4ddcacb6 100644
--- a/db/migrations/migrate.go
+++ b/db/migrations/migrate.go
@@ -36,6 +36,7 @@ func Migrate(gormDB *gorm.DB) error {
_202508041738_app_last_used,
_202508041739_response_events_index,
_202508151405_swap_xpub,
+ _202508192137_forwards,
})
return m.Migrate()
diff --git a/db/models.go b/db/models.go
index 31b73302..65567f3a 100644
--- a/db/models.go
+++ b/db/models.go
@@ -114,6 +114,14 @@ type Swap struct {
UpdatedAt time.Time
}
+type Forward struct {
+ ID uint
+ OutboundAmountForwardedMsat uint64
+ TotalFeeEarnedMsat uint64
+ CreatedAt time.Time
+ UpdatedAt time.Time
+}
+
const (
REQUEST_EVENT_STATE_HANDLER_EXECUTING = "executing"
REQUEST_EVENT_STATE_HANDLER_EXECUTED = "executed"
diff --git a/frontend/src/components/RoutingFeeDialogContent.tsx b/frontend/src/components/RoutingFeeDialogContent.tsx
index 0f0021b9..080b1fe0 100644
--- a/frontend/src/components/RoutingFeeDialogContent.tsx
+++ b/frontend/src/components/RoutingFeeDialogContent.tsx
@@ -22,18 +22,26 @@ type Props = {
};
export function RoutingFeeDialogContent({ channel }: Props) {
- const currentFee: number = React.useMemo(() => {
- return Math.floor(channel.forwardingFeeBaseMsat / 1000);
- }, [channel.forwardingFeeBaseMsat]);
- const [forwardingFee, setForwardingFee] = React.useState(
- currentFee ? currentFee.toString() : ""
+ const currentBaseFeeSats: number = Math.floor(
+ channel.forwardingFeeBaseMsat / 1000
+ );
+ const currentFeePPM: number = channel.forwardingFeeProportionalMillionths;
+
+ const [baseFeeSats, setBaseFeeSats] = React.useState(
+ currentBaseFeeSats !== undefined ? currentBaseFeeSats.toString() : ""
+ );
+ const [
+ forwardingFeeProportionalMillionths,
+ setForwardingFeeProportionalMillionths,
+ ] = React.useState(
+ currentFeePPM !== undefined ? currentFeePPM.toString() : ""
);
const { toast } = useToast();
const { mutate: reloadChannels } = useChannels();
async function updateFee() {
try {
- const forwardingFeeBaseMsat = +forwardingFee * 1000;
+ const forwardingFeeBaseMsat = +baseFeeSats * 1000;
console.info(
`🎬 Updating channel ${channel.id} with ${channel.remotePubkey}`
@@ -48,6 +56,8 @@ export function RoutingFeeDialogContent({ channel }: Props) {
},
body: JSON.stringify({
forwardingFeeBaseMsat: forwardingFeeBaseMsat,
+ forwardingFeeProportionalMillionths:
+ +forwardingFeeProportionalMillionths,
} as UpdateChannelRequest),
}
);
@@ -74,7 +84,7 @@ export function RoutingFeeDialogContent({ channel }: Props) {
unwanted routing. No matter the fee, you can still receive payments.{" "}
{
- setForwardingFee(e.target.value.trim());
+ setBaseFeeSats(e.target.value.trim());
+ }}
+ />
+
+ {
+ setForwardingFeeProportionalMillionths(e.target.value.trim());
}}
/>
Cancel
Confirm
diff --git a/frontend/src/components/home/widgets/ForwardsWidget.tsx b/frontend/src/components/home/widgets/ForwardsWidget.tsx
new file mode 100644
index 00000000..2636e307
--- /dev/null
+++ b/frontend/src/components/home/widgets/ForwardsWidget.tsx
@@ -0,0 +1,69 @@
+import FormattedFiatAmount from "src/components/FormattedFiatAmount";
+import {
+ Card,
+ CardContent,
+ CardHeader,
+ CardTitle,
+} from "src/components/ui/card";
+import { ExternalLinkButton } from "src/components/ui/custom/external-link-button";
+import { useForwards } from "src/hooks/useForwards";
+
+export function ForwardsWidget() {
+ const { data: forwards } = useForwards();
+
+ if (!forwards) {
+ return null;
+ }
+
+ return (
+
+
+ Routing
+
+
+
+
+
Fees Earned
+
+ {new Intl.NumberFormat().format(
+ Math.floor(forwards.totalFeeEarnedMsat / 1000)
+ )}{" "}
+ sats
+
+
+
+
+
Total Routed
+
+ {new Intl.NumberFormat().format(
+ Math.floor(forwards.outboundAmountForwardedMsat / 1000)
+ )}{" "}
+ sats
+
+
+
+
+
Transactions Routed
+
{forwards.numForwards}
+
+
+
+
+ Earn and support the lightning network by routing payments. To route
+ payments you need public channels and set competitive fees.
+
+
+ Learn more
+
+
+
+
+ );
+}
diff --git a/frontend/src/hooks/useForwards.ts b/frontend/src/hooks/useForwards.ts
new file mode 100644
index 00000000..d491bbf1
--- /dev/null
+++ b/frontend/src/hooks/useForwards.ts
@@ -0,0 +1,8 @@
+import useSWR from "swr";
+
+import { GetForwardsResponse } from "src/types";
+import { swrFetcher } from "src/utils/swr";
+
+export function useForwards() {
+ return useSWR("/api/forwards", swrFetcher);
+}
diff --git a/frontend/src/screens/Home.tsx b/frontend/src/screens/Home.tsx
index fdc54f16..367701d4 100644
--- a/frontend/src/screens/Home.tsx
+++ b/frontend/src/screens/Home.tsx
@@ -23,6 +23,7 @@ import albyGo from "src/assets/suggested-apps/alby-go.png";
import zapplanner from "src/assets/suggested-apps/zapplanner.png";
import { AppOfTheDayWidget } from "src/components/home/widgets/AppOfTheDayWidget";
import { BlockHeightWidget } from "src/components/home/widgets/BlockHeightWidget";
+import { ForwardsWidget } from "src/components/home/widgets/ForwardsWidget";
import { LatestUsedAppsWidget } from "src/components/home/widgets/LatestUsedAppsWidget";
import { LightningMessageboardWidget } from "src/components/home/widgets/LightningMessageboardWidget";
import { NodeStatusWidget } from "src/components/home/widgets/NodeStatusWidget";
@@ -204,6 +205,7 @@ function Home() {
+
)}
diff --git a/frontend/src/types.ts b/frontend/src/types.ts
index 5c353e6e..dcea21ee 100644
--- a/frontend/src/types.ts
+++ b/frontend/src/types.ts
@@ -297,6 +297,7 @@ export type Channel = {
confirmations?: number;
confirmationsRequired?: number;
forwardingFeeBaseMsat: number;
+ forwardingFeeProportionalMillionths: number;
unspendablePunishmentReserve: number;
counterpartyUnspendablePunishmentReserve: number;
error?: string;
@@ -647,3 +648,9 @@ export type NewChannelOrder = OnchainOrder | LightningOrder;
export type AuthTokenResponse = {
token: string;
};
+
+export type GetForwardsResponse = {
+ outboundAmountForwardedMsat: number;
+ totalFeeEarnedMsat: number;
+ numForwards: number;
+};
diff --git a/http/http_service.go b/http/http_service.go
index 1fb98abd..661c6a40 100644
--- a/http/http_service.go
+++ b/http/http_service.go
@@ -181,6 +181,7 @@ func (httpSvc *HttpService) RegisterSharedRoutes(e *echo.Echo) {
restrictedApiGroup.POST("/autoswap", httpSvc.enableAutoSwapOutHandler)
restrictedApiGroup.DELETE("/autoswap", httpSvc.disableAutoSwapOutHandler)
restrictedApiGroup.POST("/node/alias", httpSvc.setNodeAliasHandler)
+ restrictedApiGroup.GET("/forwards", httpSvc.forwardsHandler)
httpSvc.albyHttpSvc.RegisterSharedRoutes(restrictedApiGroup, e)
}
@@ -1491,3 +1492,14 @@ func (httpSvc *HttpService) setNodeAliasHandler(c echo.Context) error {
return c.NoContent(http.StatusNoContent)
}
+
+func (httpSvc *HttpService) forwardsHandler(c echo.Context) error {
+ forwards, err := httpSvc.api.GetForwards()
+ if err != nil {
+ return c.JSON(http.StatusInternalServerError, ErrorResponse{
+ Message: fmt.Sprintf("Failed to get forwards: %s", err.Error()),
+ })
+ }
+
+ return c.JSON(http.StatusOK, forwards)
+}
diff --git a/lnclient/ldk/ldk.go b/lnclient/ldk/ldk.go
index 0e5d65d0..93454879 100644
--- a/lnclient/ldk/ldk.go
+++ b/lnclient/ldk/ldk.go
@@ -972,6 +972,7 @@ func (ls *LDKService) ListChannels(ctx context.Context) ([]lnclient.Channel, err
Confirmations: ldkChannel.Confirmations,
ConfirmationsRequired: ldkChannel.ConfirmationsRequired,
ForwardingFeeBaseMsat: ldkChannel.Config.ForwardingFeeBaseMsat,
+ ForwardingFeeProportionalMillionths: ldkChannel.Config.ForwardingFeeProportionalMillionths,
UnspendablePunishmentReserve: unspendablePunishmentReserve,
CounterpartyUnspendablePunishmentReserve: ldkChannel.CounterpartyUnspendablePunishmentReserve,
Error: channelError,
@@ -1121,6 +1122,7 @@ func (ls *LDKService) UpdateChannel(ctx context.Context, updateChannelRequest *l
existingConfig := foundChannel.Config
existingConfig.ForwardingFeeBaseMsat = updateChannelRequest.ForwardingFeeBaseMsat
+ existingConfig.ForwardingFeeProportionalMillionths = updateChannelRequest.ForwardingFeeProportionalMillionths
if updateChannelRequest.MaxDustHtlcExposureFromFeeRateMultiplier > 0 {
existingConfig.MaxDustHtlcExposure = ldk_node.MaxDustHtlcExposureFeeRateMultiplier{
@@ -1765,6 +1767,20 @@ func (ls *LDKService) handleLdkEvent(event *ldk_node.Event) {
"total_fee_earned_msat": eventType.TotalFeeEarnedMsat,
"outbound_amount_forwarded_msat": eventType.OutboundAmountForwardedMsat,
}).Info("LDK Payment forwarded")
+ if eventType.TotalFeeEarnedMsat == nil || eventType.OutboundAmountForwardedMsat == nil {
+ logger.Logger.WithFields(logrus.Fields{
+ "earned_msat": eventType.TotalFeeEarnedMsat,
+ "outbound_amount_forwarded_msat": eventType.OutboundAmountForwardedMsat,
+ }).Error("forwarded payment has missing required fields")
+ return
+ }
+ ls.eventPublisher.Publish(&events.Event{
+ Event: "nwc_payment_forwarded",
+ Properties: &lnclient.PaymentForwardedEventProperties{
+ TotalFeeEarnedMsat: *eventType.TotalFeeEarnedMsat,
+ OutboundAmountForwardedMsat: *eventType.OutboundAmountForwardedMsat,
+ },
+ })
case ldk_node.EventPaymentClaimable:
if eventType.ClaimDeadline == nil {
diff --git a/lnclient/lnd/lnd.go b/lnclient/lnd/lnd.go
index be0d5903..1a2b266c 100644
--- a/lnclient/lnd/lnd.go
+++ b/lnclient/lnd/lnd.go
@@ -96,12 +96,45 @@ func NewLNDService(ctx context.Context, eventPublisher events.EventPublisher, ln
go lndService.subscribeInvoices(lndCtx)
go lndService.subscribeChannelEvents(lndCtx)
go lndService.subscribeOpenHoldInvoices(lndCtx)
+ go lndService.trackForwardedPayments(lndCtx)
logger.Logger.WithField("alias", nodeInfo.Alias).Info("Connected to LND")
return lndService, nil
}
+func (svc *LNDService) trackForwardedPayments(ctx context.Context) {
+ // NOTE: this only tracks payments when hub is online and attached
+ lastTime := time.Now()
+ for {
+ select {
+ case <-ctx.Done():
+ return
+ default:
+ time.Sleep(1 * time.Minute)
+ nextTime := time.Now()
+ forwardedPayments, err := svc.client.ForwardingHistory(ctx, &lnrpc.ForwardingHistoryRequest{
+ StartTime: uint64(lastTime.Unix()),
+ EndTime: uint64(nextTime.Unix()),
+ })
+ if err != nil {
+ logger.Logger.WithError(err).Error("failed to read forwarding history")
+ continue
+ }
+ for _, forwardingEvent := range forwardedPayments.ForwardingEvents {
+ svc.eventPublisher.Publish(&events.Event{
+ Event: "nwc_payment_forwarded",
+ Properties: &lnclient.PaymentForwardedEventProperties{
+ TotalFeeEarnedMsat: forwardingEvent.FeeMsat,
+ OutboundAmountForwardedMsat: forwardingEvent.AmtOutMsat,
+ },
+ })
+ }
+ lastTime = nextTime
+ }
+ }
+}
+
func (svc *LNDService) subscribePayments(ctx context.Context) {
for {
select {
@@ -867,7 +900,8 @@ func (svc *LNDService) ListChannels(ctx context.Context) ([]lnclient.Channel, er
channelOpeningBlockHeight := lndChannel.ChanId >> 40
confirmations := nodeInfo.BlockHeight - uint32(channelOpeningBlockHeight) + 1
- var forwardingFee uint32
+ var forwardingFeeBaseMsat uint32
+ var forwardingFeeProportionalMillionths uint32
if !lndChannel.Private {
channelEdge, err := svc.client.GetChanInfo(ctx, &lnrpc.ChanInfoRequest{
ChanId: lndChannel.ChanId,
@@ -883,7 +917,8 @@ func (svc *LNDService) ListChannels(ctx context.Context) ([]lnclient.Channel, er
policy = channelEdge.Node2Policy
}
if policy != nil {
- forwardingFee = uint32(policy.FeeBaseMsat)
+ forwardingFeeBaseMsat = uint32(policy.FeeBaseMsat)
+ forwardingFeeProportionalMillionths = uint32(policy.FeeRateMilliMsat)
}
}
@@ -903,7 +938,8 @@ func (svc *LNDService) ListChannels(ctx context.Context) ([]lnclient.Channel, er
UnspendablePunishmentReserve: lndChannel.LocalConstraints.ChanReserveSat,
CounterpartyUnspendablePunishmentReserve: lndChannel.RemoteConstraints.ChanReserveSat,
IsOutbound: lndChannel.Initiator,
- ForwardingFeeBaseMsat: forwardingFee,
+ ForwardingFeeBaseMsat: forwardingFeeBaseMsat,
+ ForwardingFeeProportionalMillionths: forwardingFeeProportionalMillionths,
}
}
@@ -1101,7 +1137,7 @@ func (svc *LNDService) UpdateChannel(ctx context.Context, updateChannelRequest *
ChanPoint: channelPoint,
},
BaseFeeMsat: int64(updateChannelRequest.ForwardingFeeBaseMsat),
- FeeRatePpm: uint32(nodePolicy.FeeRateMilliMsat),
+ FeeRatePpm: updateChannelRequest.ForwardingFeeProportionalMillionths,
TimeLockDelta: nodePolicy.TimeLockDelta,
MaxHtlcMsat: nodePolicy.MaxHtlcMsat,
})
diff --git a/lnclient/lnd/wrapper/lnd.go b/lnclient/lnd/wrapper/lnd.go
index c50d1b98..4efd0f12 100644
--- a/lnclient/lnd/wrapper/lnd.go
+++ b/lnclient/lnd/wrapper/lnd.go
@@ -243,3 +243,7 @@ func (wrapper *LNDWrapper) DisconnectPeer(ctx context.Context, req *lnrpc.Discon
func (wrapper *LNDWrapper) SubscribeChannelEvents(ctx context.Context, in *lnrpc.ChannelEventSubscription, options ...grpc.CallOption) (lnrpc.Lightning_SubscribeChannelEventsClient, error) {
return wrapper.client.SubscribeChannelEvents(ctx, in, options...)
}
+
+func (wrapper *LNDWrapper) ForwardingHistory(ctx context.Context, in *lnrpc.ForwardingHistoryRequest, options ...grpc.CallOption) (*lnrpc.ForwardingHistoryResponse, error) {
+ return wrapper.client.ForwardingHistory(ctx, in, options...)
+}
diff --git a/lnclient/models.go b/lnclient/models.go
index 027bafe8..09b1393d 100644
--- a/lnclient/models.go
+++ b/lnclient/models.go
@@ -111,6 +111,7 @@ type Channel struct {
Confirmations *uint32
ConfirmationsRequired *uint32
ForwardingFeeBaseMsat uint32
+ ForwardingFeeProportionalMillionths uint32
UnspendablePunishmentReserve uint64
CounterpartyUnspendablePunishmentReserve uint64
Error *string
@@ -148,6 +149,7 @@ type UpdateChannelRequest struct {
ChannelId string `json:"channelId"`
NodeId string `json:"nodeId"`
ForwardingFeeBaseMsat uint32 `json:"forwardingFeeBaseMsat"`
+ ForwardingFeeProportionalMillionths uint32 `json:"forwardingFeeProportionalMillionths"`
MaxDustHtlcExposureFromFeeRateMultiplier uint64 `json:"maxDustHtlcExposureFromFeeRateMultiplier"`
}
@@ -214,6 +216,11 @@ type PaymentFailedEventProperties struct {
Reason string
}
+type PaymentForwardedEventProperties struct {
+ TotalFeeEarnedMsat uint64
+ OutboundAmountForwardedMsat uint64
+}
+
type CustomNodeCommandArgDef struct {
Name string
Description string
diff --git a/service/payment_forwarded_consumer.go b/service/payment_forwarded_consumer.go
new file mode 100644
index 00000000..16914626
--- /dev/null
+++ b/service/payment_forwarded_consumer.go
@@ -0,0 +1,38 @@
+package service
+
+import (
+ "context"
+
+ "gorm.io/gorm"
+
+ "github.com/getAlby/hub/db"
+ "github.com/getAlby/hub/events"
+ "github.com/getAlby/hub/lnclient"
+ "github.com/getAlby/hub/logger"
+)
+
+type paymentForwardedConsumer struct {
+ events.EventSubscriber
+ db *gorm.DB
+}
+
+// When a new app is created, subscribe to it on the relay
+func (c *paymentForwardedConsumer) ConsumeEvent(ctx context.Context, event *events.Event, globalProperties map[string]interface{}) {
+ if event.Event != "nwc_payment_forwarded" {
+ return
+ }
+
+ properties, ok := event.Properties.(*lnclient.PaymentForwardedEventProperties)
+ if !ok {
+ logger.Logger.WithField("event", event).Error("Failed to cast event.Properties to payment forwarded event properties")
+ return
+ }
+ forward := &db.Forward{
+ OutboundAmountForwardedMsat: properties.OutboundAmountForwardedMsat,
+ TotalFeeEarnedMsat: properties.TotalFeeEarnedMsat,
+ }
+ err := c.db.Create(forward).Error
+ if err != nil {
+ logger.Logger.WithError(err).Error("failed to save forward to db")
+ }
+}
diff --git a/service/service.go b/service/service.go
index f4b6c67a..ffce2138 100644
--- a/service/service.go
+++ b/service/service.go
@@ -137,6 +137,9 @@ func NewService(ctx context.Context) (*service, error) {
eventPublisher.RegisterSubscriber(svc.transactionsService)
eventPublisher.RegisterSubscriber(svc.nip47Service)
eventPublisher.RegisterSubscriber(svc.albyOAuthSvc)
+ eventPublisher.RegisterSubscriber(&paymentForwardedConsumer{
+ db: gormDB,
+ })
eventPublisher.Publish(&events.Event{
Event: "nwc_started",
diff --git a/wails/wails_handlers.go b/wails/wails_handlers.go
index 83402f95..1c6ac5be 100644
--- a/wails/wails_handlers.go
+++ b/wails/wails_handlers.go
@@ -1258,6 +1258,12 @@ func (app *WailsApp) WailsRequestRouter(route string, method string, body string
}
return WailsRequestRouterResponse{Body: nil, Error: ""}
}
+ case "/api/forwards":
+ forwards, err := app.api.GetForwards()
+ if err != nil {
+ return WailsRequestRouterResponse{Body: nil, Error: err.Error()}
+ }
+ return WailsRequestRouterResponse{Body: forwards, Error: ""}
}
lightningAddressRegex := regexp.MustCompile(