package lndclient import ( "context" "github.com/btcsuite/btcd/btcec" "github.com/btcsuite/btcd/wire" "github.com/btcsuite/btcutil" "github.com/lightninglabs/loop/swap" "github.com/lightningnetwork/lnd/keychain" "github.com/lightningnetwork/lnd/lnrpc/signrpc" "github.com/lightningnetwork/lnd/lnrpc/walletrpc" "github.com/lightningnetwork/lnd/lnwallet/chainfee" "google.golang.org/grpc" ) // WalletKitClient exposes wallet functionality. type WalletKitClient interface { DeriveNextKey(ctx context.Context, family int32) ( *keychain.KeyDescriptor, error) DeriveKey(ctx context.Context, locator *keychain.KeyLocator) ( *keychain.KeyDescriptor, error) NextAddr(ctx context.Context) (btcutil.Address, error) PublishTransaction(ctx context.Context, tx *wire.MsgTx) error SendOutputs(ctx context.Context, outputs []*wire.TxOut, feeRate chainfee.SatPerKWeight) (*wire.MsgTx, error) EstimateFee(ctx context.Context, confTarget int32) (chainfee.SatPerKWeight, error) } type walletKitClient struct { client walletrpc.WalletKitClient walletKitMac serializedMacaroon } func newWalletKitClient(conn *grpc.ClientConn, walletKitMac serializedMacaroon) *walletKitClient { return &walletKitClient{ client: walletrpc.NewWalletKitClient(conn), walletKitMac: walletKitMac, } } func (m *walletKitClient) DeriveNextKey(ctx context.Context, family int32) ( *keychain.KeyDescriptor, error) { rpcCtx, cancel := context.WithTimeout(ctx, rpcTimeout) defer cancel() rpcCtx = m.walletKitMac.WithMacaroonAuth(rpcCtx) resp, err := m.client.DeriveNextKey(rpcCtx, &walletrpc.KeyReq{ KeyFamily: family, }) if err != nil { return nil, err } key, err := btcec.ParsePubKey(resp.RawKeyBytes, btcec.S256()) if err != nil { return nil, err } return &keychain.KeyDescriptor{ KeyLocator: keychain.KeyLocator{ Family: keychain.KeyFamily(resp.KeyLoc.KeyFamily), Index: uint32(resp.KeyLoc.KeyIndex), }, PubKey: key, }, nil } func (m *walletKitClient) DeriveKey(ctx context.Context, in *keychain.KeyLocator) ( *keychain.KeyDescriptor, error) { rpcCtx, cancel := context.WithTimeout(ctx, rpcTimeout) defer cancel() rpcCtx = m.walletKitMac.WithMacaroonAuth(rpcCtx) resp, err := m.client.DeriveKey(rpcCtx, &signrpc.KeyLocator{ KeyFamily: int32(in.Family), KeyIndex: int32(in.Index), }) if err != nil { return nil, err } key, err := btcec.ParsePubKey(resp.RawKeyBytes, btcec.S256()) if err != nil { return nil, err } return &keychain.KeyDescriptor{ KeyLocator: *in, PubKey: key, }, nil } func (m *walletKitClient) NextAddr(ctx context.Context) ( btcutil.Address, error) { rpcCtx, cancel := context.WithTimeout(ctx, rpcTimeout) defer cancel() rpcCtx = m.walletKitMac.WithMacaroonAuth(rpcCtx) resp, err := m.client.NextAddr(rpcCtx, &walletrpc.AddrRequest{}) if err != nil { return nil, err } addr, err := btcutil.DecodeAddress(resp.Addr, nil) if err != nil { return nil, err } return addr, nil } func (m *walletKitClient) PublishTransaction(ctx context.Context, tx *wire.MsgTx) error { txHex, err := swap.EncodeTx(tx) if err != nil { return err } rpcCtx, cancel := context.WithTimeout(ctx, rpcTimeout) defer cancel() rpcCtx = m.walletKitMac.WithMacaroonAuth(rpcCtx) _, err = m.client.PublishTransaction(rpcCtx, &walletrpc.Transaction{ TxHex: txHex, }) return err } func (m *walletKitClient) SendOutputs(ctx context.Context, outputs []*wire.TxOut, feeRate chainfee.SatPerKWeight) ( *wire.MsgTx, error) { rpcOutputs := make([]*signrpc.TxOut, len(outputs)) for i, output := range outputs { rpcOutputs[i] = &signrpc.TxOut{ PkScript: output.PkScript, Value: output.Value, } } rpcCtx, cancel := context.WithTimeout(ctx, rpcTimeout) defer cancel() rpcCtx = m.walletKitMac.WithMacaroonAuth(rpcCtx) resp, err := m.client.SendOutputs(rpcCtx, &walletrpc.SendOutputsRequest{ Outputs: rpcOutputs, SatPerKw: int64(feeRate), }) if err != nil { return nil, err } tx, err := swap.DecodeTx(resp.RawTx) if err != nil { return nil, err } return tx, nil } func (m *walletKitClient) EstimateFee(ctx context.Context, confTarget int32) ( chainfee.SatPerKWeight, error) { rpcCtx, cancel := context.WithTimeout(ctx, rpcTimeout) defer cancel() rpcCtx = m.walletKitMac.WithMacaroonAuth(rpcCtx) resp, err := m.client.EstimateFee(rpcCtx, &walletrpc.EstimateFeeRequest{ ConfTarget: int32(confTarget), }) if err != nil { return 0, err } return chainfee.SatPerKWeight(resp.SatPerKw), nil }