Skip to content
This repository has been archived by the owner on Sep 2, 2024. It is now read-only.

Commit

Permalink
chore: move address storage to backend and return last unused address
Browse files Browse the repository at this point in the history
  • Loading branch information
rolznz committed Jun 10, 2024
1 parent 32b2c8c commit 5728803
Show file tree
Hide file tree
Showing 11 changed files with 204 additions and 146 deletions.
49 changes: 47 additions & 2 deletions api/api.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import (

"github.com/getAlby/nostr-wallet-connect/alby"
"github.com/getAlby/nostr-wallet-connect/backup"
"github.com/getAlby/nostr-wallet-connect/config"
"github.com/getAlby/nostr-wallet-connect/db"
"github.com/getAlby/nostr-wallet-connect/lnclient"
"github.com/getAlby/nostr-wallet-connect/lsp"
Expand Down Expand Up @@ -375,19 +376,62 @@ func (api *api) CloseChannel(ctx context.Context, peerId, channelId string, forc
})
}

func (api *api) GetNewOnchainAddress(ctx context.Context) (*NewOnchainAddressResponse, error) {
func (api *api) GetNewOnchainAddress(ctx context.Context) (*OnchainAddressResponse, error) {
if api.svc.GetLNClient() == nil {
return nil, errors.New("LNClient not started")
}
address, err := api.svc.GetLNClient().GetNewOnchainAddress(ctx)
if err != nil {
return nil, err
}
return &NewOnchainAddressResponse{

api.svc.GetConfig().SetUpdate(config.OnchainAddressKey, address, "")

return &OnchainAddressResponse{
Address: address,
}, nil
}

func (api *api) GetLastUnusedOnchainAddress(ctx context.Context) (string, error) {
if api.svc.GetLNClient() == nil {
return "", errors.New("LNClient not started")
}

currentAddress, err := api.svc.GetConfig().Get(config.OnchainAddressKey, "")
if err != nil {
api.logger.WithError(err).Error("Failed to get current address from config")
return "", err
}

if currentAddress != "" {
// check if address has any transactions
response, err := api.RequestEsploraApi("/address/" + currentAddress + "/txs")
if err != nil {
api.logger.WithError(err).Error("Failed to get current address transactions")
return "", err
}

api.logger.WithField("response", response).Info("Got address response")
transactions, ok := response.([]interface{})
if !ok {
err = fmt.Errorf("Failed to cast esplora address txs response: %v", response)
return "", err
}

if len(transactions) == 0 {
// address has not been used yet
return currentAddress, nil
}
}

newAddress, err := api.GetNewOnchainAddress(ctx)
if err != nil {
api.logger.WithError(err).Error("Failed to retrieve new onchain address")
return "", err
}
return newAddress.Address, nil
}

func (api *api) SignMessage(ctx context.Context, message string) (*SignMessageResponse, error) {
if api.svc.GetLNClient() == nil {
return nil, errors.New("LNClient not started")
Expand Down Expand Up @@ -426,6 +470,7 @@ func (api *api) GetBalances(ctx context.Context) (*BalancesResponse, error) {
return balances, nil
}

// TODO: remove dependency on this endpoint
func (api *api) RequestMempoolApi(endpoint string) (interface{}, error) {
url := api.svc.GetConfig().GetEnv().MempoolApi + endpoint

Expand Down
56 changes: 56 additions & 0 deletions api/esplora.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
package api

import (
"encoding/json"
"errors"
"fmt"
"io"
"net/http"
"time"

"github.com/sirupsen/logrus"
)

func (api *api) RequestEsploraApi(endpoint string) (interface{}, error) {
url := api.svc.GetConfig().GetEnv().LDKEsploraServer + endpoint

client := http.Client{
Timeout: time.Second * 10,
}

req, err := http.NewRequest(http.MethodGet, url, nil)
if err != nil {
api.logger.WithError(err).WithFields(logrus.Fields{
"url": url,
}).Error("Failed to create http request")
return nil, err
}

res, err := client.Do(req)
if err != nil {
api.logger.WithError(err).WithFields(logrus.Fields{
"url": url,
}).Error("Failed to send request")
return nil, err
}

defer res.Body.Close()

body, readErr := io.ReadAll(res.Body)
if readErr != nil {
api.logger.WithError(err).WithFields(logrus.Fields{
"url": url,
}).Error("Failed to read response body")
return nil, errors.New("failed to read response body")
}

var jsonContent interface{}
jsonErr := json.Unmarshal(body, &jsonContent)
if jsonErr != nil {
api.logger.WithError(jsonErr).WithFields(logrus.Fields{
"url": url,
}).Error("Failed to deserialize json")
return nil, fmt.Errorf("failed to deserialize json %s %s", url, string(body))
}
return jsonContent, nil
}
7 changes: 4 additions & 3 deletions api/models.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,8 @@ type API interface {
DisconnectPeer(ctx context.Context, peerId string) error
OpenChannel(ctx context.Context, openChannelRequest *OpenChannelRequest) (*OpenChannelResponse, error)
CloseChannel(ctx context.Context, peerId, channelId string, force bool) (*CloseChannelResponse, error)
GetNewOnchainAddress(ctx context.Context) (*NewOnchainAddressResponse, error)
GetNewOnchainAddress(ctx context.Context) (*OnchainAddressResponse, error)
GetLastUnusedOnchainAddress(ctx context.Context) (string, error)
SignMessage(ctx context.Context, message string) (*SignMessageResponse, error)
RedeemOnchainFunds(ctx context.Context, toAddress string) (*RedeemOnchainFundsResponse, error)
GetBalances(ctx context.Context) (*BalancesResponse, error)
Expand Down Expand Up @@ -98,7 +99,7 @@ type BackupReminderRequest struct {
}

type SetupRequest struct {
LNBackendType string `json:"backendType"`
LNBackendType string `json:"backendType"`
UnlockPassword string `json:"unlockPassword"`

// Breez / Greenlight
Expand Down Expand Up @@ -174,7 +175,7 @@ type RedeemOnchainFundsResponse struct {
type OnchainBalanceResponse = lnclient.OnchainBalanceResponse
type BalancesResponse = lnclient.BalancesResponse

type NewOnchainAddressResponse struct {
type OnchainAddressResponse struct {
Address string `json:"address"`
}

Expand Down
6 changes: 5 additions & 1 deletion config/models.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,10 @@ const (
CashuBackendType = "CASHU"
)

const (
OnchainAddressKey = "OnchainAddress"
)

type AppConfig struct {
Relay string `envconfig:"RELAY" default:"wss://relay.getalby.com/v1"`
LNBackendType string `envconfig:"LN_BACKEND_TYPE"`
Expand All @@ -21,7 +25,7 @@ type AppConfig struct {
CookieSecret string `envconfig:"COOKIE_SECRET"`
LogLevel string `envconfig:"LOG_LEVEL"`
LDKNetwork string `envconfig:"LDK_NETWORK" default:"bitcoin"`
LDKEsploraServer string `envconfig:"LDK_ESPLORA_SERVER" default:"https://electrs.albylabs.com"`
LDKEsploraServer string `envconfig:"LDK_ESPLORA_SERVER" default:"https://electrs.albylabs.com"` // TODO: remove LDK prefix
LDKGossipSource string `envconfig:"LDK_GOSSIP_SOURCE" default:"https://rapidsync.lightningdevkit.org/snapshot"`
LDKLogLevel string `envconfig:"LDK_LOG_LEVEL"`
MempoolApi string `envconfig:"MEMPOOL_API" default:"https://mempool.space/api"`
Expand Down
1 change: 0 additions & 1 deletion frontend/src/constants.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
export const localStorageKeys = {
returnTo: "returnTo",
onchainAddress: "onchainAddress",
channelOrder: "channelOrder",
};

Expand Down
49 changes: 49 additions & 0 deletions frontend/src/hooks/useOnchainAddress.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
import useSWR from "swr";

import React from "react";
import { useCSRF } from "src/hooks/useCSRF";
import { GetOnchainAddressResponse } from "src/types";
import { request } from "src/utils/request";
import { swrFetcher } from "src/utils/swr";

export function useOnchainAddress() {
const { data: csrf } = useCSRF();
const swr = useSWR<string>("/api/wallet/address", swrFetcher);
const [isLoading, setLoading] = React.useState(false);

const getNewAddress = React.useCallback(async () => {
if (!csrf) {
return;
}
setLoading(true);
try {
const response = await request<GetOnchainAddressResponse>(
"/api/wallet/new-address",
{
method: "POST",
headers: {
"X-CSRF-Token": csrf,
"Content-Type": "application/json",
},
}
);
if (!response?.address) {
throw new Error("No address in response");
}
swr.mutate(response.address, false);
} catch (error) {
alert("Failed to request a new address: " + error);
} finally {
setLoading(false);
}
}, [csrf, swr]);

return React.useMemo(
() => ({
...swr,
getNewAddress,
loadingAddress: isLoading || !swr.data,
}),
[swr, getNewAddress, isLoading]
);
}
55 changes: 9 additions & 46 deletions frontend/src/screens/channels/CurrentChannelOrder.tsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,7 @@
import React from "react";
import { localStorageKeys } from "src/constants";
import {
Channel,
ConnectPeerRequest,
GetOnchainAddressResponse,
NewChannelOrder,
Node,
OpenChannelRequest,
Expand Down Expand Up @@ -48,6 +46,7 @@ import { useBalances } from "src/hooks/useBalances";
import { useCSRF } from "src/hooks/useCSRF";
import { useChannels } from "src/hooks/useChannels";
import { useMempoolApi } from "src/hooks/useMempoolApi";
import { useOnchainAddress } from "src/hooks/useOnchainAddress";
import { usePeers } from "src/hooks/usePeers";
import { useSyncWallet } from "src/hooks/useSyncWallet";
import { copyToClipboard } from "src/lib/clipboard";
Expand Down Expand Up @@ -205,56 +204,20 @@ function PayBitcoinChannelOrderTopup({ order }: { order: NewChannelOrder }) {
}

const { data: channels } = useChannels();
const { data: csrf } = useCSRF();

const { data: balances } = useBalances();
const [onchainAddress, setOnchainAddress] = React.useState<string>();
const [isLoading, setLoading] = React.useState(false);
const {
data: onchainAddress,
getNewAddress,
loadingAddress,
} = useOnchainAddress();

const { data: mempoolAddressUtxos } = useMempoolApi<{ value: number }[]>(
onchainAddress ? `/address/${onchainAddress}/utxo` : undefined,
true
);
const estimatedTransactionFee = useEstimatedTransactionFee();

const getNewAddress = React.useCallback(async () => {
if (!csrf) {
return;
}
setLoading(true);
try {
const response = await request<GetOnchainAddressResponse>(
"/api/wallet/new-address",
{
method: "POST",
headers: {
"X-CSRF-Token": csrf,
"Content-Type": "application/json",
},
//body: JSON.stringify({}),
}
);
if (!response?.address) {
throw new Error("No address in response");
}
localStorage.setItem(localStorageKeys.onchainAddress, response.address);
setOnchainAddress(response.address);
} catch (error) {
alert("Failed to request a new address: " + error);
} finally {
setLoading(false);
}
}, [csrf]);

React.useEffect(() => {
const existingAddress = localStorage.getItem(
localStorageKeys.onchainAddress
);
if (existingAddress) {
setOnchainAddress(existingAddress);
return;
}
getNewAddress();
}, [getNewAddress]);

if (!onchainAddress || !balances || !estimatedTransactionFee) {
return (
<div className="flex justify-center">
Expand Down Expand Up @@ -344,7 +307,7 @@ function PayBitcoinChannelOrderTopup({ order }: { order: NewChannelOrder }) {
variant="secondary"
size="icon"
onClick={getNewAddress}
loading={isLoading}
loading={loadingAddress}
>
<RefreshCw className="w-4 h-4" />
</LoadingButton>
Expand Down
Loading

0 comments on commit 5728803

Please sign in to comment.