github.com/mit-dci/lit@v0.0.0-20221102210550-8c3d3b49f2ce/litrpc/walletcmds.go (about)

     1  package litrpc
     2  
     3  import (
     4  	"fmt"
     5  
     6  	"github.com/mit-dci/lit/bech32"
     7  	"github.com/mit-dci/lit/consts"
     8  	"github.com/mit-dci/lit/lnutil"
     9  	"github.com/mit-dci/lit/logging"
    10  	"github.com/mit-dci/lit/portxo"
    11  	"github.com/mit-dci/lit/wire"
    12  )
    13  
    14  type TxidsReply struct {
    15  	Txids []string
    16  }
    17  type StatusReply struct {
    18  	Status string
    19  }
    20  
    21  type NoArgs struct {
    22  	// nothin
    23  }
    24  
    25  type CoinArgs struct {
    26  	CoinType uint32
    27  }
    28  
    29  // ------------------------- balance
    30  // BalReply is the reply when the user asks about their balance.
    31  type CoinBalReply struct {
    32  	CoinType    uint32
    33  	SyncHeight  int32 // height this wallet is synced to
    34  	ChanTotal   int64 // total balance in channels
    35  	TxoTotal    int64 // all utxos
    36  	MatureWitty int64 // confirmed, spendable and witness
    37  	FeeRate     int64 // fee per byte
    38  }
    39  
    40  type BalanceReply struct {
    41  	Balances []CoinBalReply
    42  }
    43  
    44  func (r *LitRPC) Balance(args *NoArgs, reply *BalanceReply) error {
    45  
    46  	var allTxos portxo.TxoSliceByAmt
    47  
    48  	// get all channels
    49  	qcs, err := r.Node.GetAllQchans()
    50  	if err != nil {
    51  		return err
    52  	}
    53  
    54  	for cointype, wal := range r.Node.SubWallet {
    55  		// will add the balance for this wallet to the full reply
    56  		var cbr CoinBalReply
    57  
    58  		cbr.CoinType = cointype
    59  		// get wallet height
    60  		cbr.SyncHeight = wal.CurrentHeight()
    61  		// also current fee rate
    62  		cbr.FeeRate = wal.Fee()
    63  
    64  		allTxos, err = wal.UtxoDump()
    65  		if err != nil {
    66  			return err
    67  		}
    68  
    69  		// ask sub-wallet for balance
    70  		cbr.TxoTotal = allTxos.Sum()
    71  		cbr.MatureWitty = allTxos.SumWitness(cbr.SyncHeight)
    72  
    73  		// iterate through channels to figure out how much we have
    74  		for _, q := range qcs {
    75  			if q.Coin() == cointype && !q.CloseData.Closed {
    76  				cbr.ChanTotal += q.State.MyAmt
    77  			}
    78  		}
    79  
    80  		// I thought slices were pointery enough that I could put this line
    81  		// near the top.  Guess not.
    82  		reply.Balances = append(reply.Balances, cbr)
    83  	}
    84  	return nil
    85  }
    86  
    87  type TxoInfo struct {
    88  	OutPoint string
    89  	Amt      int64
    90  	Height   int32
    91  	Delay    int32
    92  	CoinType string
    93  	Witty    bool
    94  
    95  	KeyPath string
    96  }
    97  type TxoListReply struct {
    98  	Txos []TxoInfo
    99  }
   100  
   101  // TxoList sends back a list of all non-channel utxos
   102  func (r *LitRPC) TxoList(args *NoArgs, reply *TxoListReply) error {
   103  
   104  	for _, wal := range r.Node.SubWallet {
   105  
   106  		walTxos, err := wal.UtxoDump()
   107  		if err != nil {
   108  			return err
   109  		}
   110  
   111  		syncHeight := wal.CurrentHeight()
   112  
   113  		theseTxos := make([]TxoInfo, len(walTxos))
   114  		for i, u := range walTxos {
   115  			theseTxos[i].OutPoint = u.Op.String()
   116  			theseTxos[i].Amt = u.Value
   117  			theseTxos[i].Height = u.Height
   118  			theseTxos[i].CoinType = wal.Params().Name
   119  			// show delay before utxo can be spent
   120  			if u.Seq != 0 {
   121  				theseTxos[i].Delay = u.Height + int32(u.Seq) - syncHeight
   122  			}
   123  			theseTxos[i].Witty = u.Mode&portxo.FlagTxoWitness != 0
   124  			theseTxos[i].KeyPath = u.KeyGen.String()
   125  		}
   126  
   127  		reply.Txos = append(reply.Txos, theseTxos...)
   128  	}
   129  	return nil
   130  }
   131  
   132  // ------------------------- send
   133  type SendArgs struct {
   134  	DestAddrs []string
   135  	Amts      []int64
   136  }
   137  
   138  func (r *LitRPC) Send(args SendArgs, reply *TxidsReply) error {
   139  	var err error
   140  
   141  	nOutputs := len(args.DestAddrs)
   142  	if nOutputs < 1 {
   143  		return fmt.Errorf("No destination address specified")
   144  	}
   145  	if nOutputs != len(args.Amts) {
   146  		return fmt.Errorf("%d addresses but %d amounts specified",
   147  			nOutputs, len(args.Amts))
   148  	}
   149  	// get cointype for first address.
   150  	coinType := CoinTypeFromAdr(args.DestAddrs[0])
   151  	// make sure we support that coin type
   152  	wal, ok := r.Node.SubWallet[coinType]
   153  	if !ok {
   154  		return fmt.Errorf("no connnected wallet for address %s type %d",
   155  			args.DestAddrs[0], coinType)
   156  	}
   157  	// All addresses must have the same cointype as they all
   158  	// must to be in the same tx.
   159  	for _, a := range args.DestAddrs {
   160  		if CoinTypeFromAdr(a) != coinType {
   161  			return fmt.Errorf("Coin type mismatch for address %s, %s",
   162  				a, args.DestAddrs[0])
   163  		}
   164  	}
   165  
   166  	txOuts := make([]*wire.TxOut, nOutputs)
   167  	for i, s := range args.DestAddrs {
   168  		if args.Amts[i] < consts.MinSendAmt {
   169  			return fmt.Errorf("Amt %d less than minimum send amount %d", args.Amts[i], consts.MinSendAmt)
   170  		}
   171  
   172  		outScript, err := AdrStringToOutscript(s)
   173  		if err != nil {
   174  			return err
   175  		}
   176  
   177  		txOuts[i] = wire.NewTxOut(args.Amts[i], outScript)
   178  	}
   179  
   180  	// we don't care if it's witness or not
   181  	ops, err := wal.MaybeSend(txOuts, false)
   182  	if err != nil {
   183  		return err
   184  	}
   185  
   186  	err = wal.ReallySend(&ops[0].Hash)
   187  	if err != nil {
   188  		return err
   189  	}
   190  
   191  	reply.Txids = append(reply.Txids, ops[0].Hash.String())
   192  	return nil
   193  }
   194  
   195  // ------------------------- sweep
   196  type SweepArgs struct {
   197  	DestAdr string
   198  	NumTx   uint32
   199  	Drop    bool
   200  }
   201  
   202  func (r *LitRPC) Sweep(args SweepArgs, reply *TxidsReply) error {
   203  	// get cointype for first address.
   204  	coinType := CoinTypeFromAdr(args.DestAdr)
   205  	// make sure we support that coin type
   206  	wal, ok := r.Node.SubWallet[coinType]
   207  	if !ok {
   208  		return fmt.Errorf("no connnected wallet for address %s type %d",
   209  			args.DestAdr, coinType)
   210  	}
   211  
   212  	outScript, err := AdrStringToOutscript(args.DestAdr)
   213  	if err != nil {
   214  		return err
   215  	}
   216  
   217  	logging.Infof("numtx: %d\n", args.NumTx)
   218  	if args.NumTx < 1 {
   219  		return fmt.Errorf("can't send %d txs", args.NumTx)
   220  	}
   221  
   222  	txids, err := wal.Sweep(outScript, args.NumTx)
   223  	if err != nil {
   224  		return err
   225  	}
   226  
   227  	for _, txid := range txids {
   228  		reply.Txids = append(reply.Txids, txid.String())
   229  	}
   230  
   231  	return nil
   232  }
   233  
   234  // ------------------------- fanout
   235  type FanArgs struct {
   236  	DestAdr      string
   237  	NumOutputs   uint32
   238  	AmtPerOutput int64
   239  }
   240  
   241  func (r *LitRPC) Fanout(args FanArgs, reply *TxidsReply) error {
   242  	if args.NumOutputs < 1 {
   243  		return fmt.Errorf("Must have at least 1 output")
   244  	}
   245  	if args.AmtPerOutput < 5000 {
   246  		return fmt.Errorf("Minimum 5000 per output")
   247  	}
   248  
   249  	// get cointype for first address.
   250  	coinType := CoinTypeFromAdr(args.DestAdr)
   251  	// make sure we support that coin type
   252  	wal, ok := r.Node.SubWallet[coinType]
   253  	if !ok {
   254  		return fmt.Errorf("no connnected wallet for address %s type %d",
   255  			args.DestAdr, coinType)
   256  	}
   257  
   258  	outScript, err := AdrStringToOutscript(args.DestAdr)
   259  	if err != nil {
   260  		return err
   261  	}
   262  
   263  	txos := make([]*wire.TxOut, args.NumOutputs)
   264  
   265  	for i, _ := range txos {
   266  		txos[i] = new(wire.TxOut)
   267  		txos[i].Value = args.AmtPerOutput + int64(i)
   268  		txos[i].PkScript = outScript
   269  	}
   270  
   271  	// don't care if inputs are witty or not
   272  	ops, err := wal.MaybeSend(txos, false)
   273  	if err != nil {
   274  		return err
   275  	}
   276  	err = wal.ReallySend(&ops[0].Hash)
   277  	if err != nil {
   278  		return err
   279  	}
   280  
   281  	reply.Txids = append(reply.Txids, ops[0].String())
   282  	return nil
   283  }
   284  
   285  // set fee
   286  type SetFeeArgs struct {
   287  	Fee      int64
   288  	CoinType uint32
   289  }
   290  
   291  // get fee
   292  type FeeArgs struct {
   293  	CoinType uint32
   294  }
   295  type FeeReply struct {
   296  	CurrentFee int64
   297  }
   298  
   299  // SetFee allows you to set a fee rate for a wallet.
   300  func (r *LitRPC) SetFee(args *SetFeeArgs, reply *FeeReply) error {
   301  	// if cointype is 0, use the node's default coin
   302  	if args.CoinType == 0 {
   303  		args.CoinType = r.Node.DefaultCoin
   304  	}
   305  	if args.Fee < 0 {
   306  		return fmt.Errorf("Invalid value for SetFee: %d", args.Fee)
   307  	}
   308  	// make sure we support that coin type
   309  	wal, ok := r.Node.SubWallet[args.CoinType]
   310  	if !ok {
   311  		return fmt.Errorf("no connnected wallet for coin type %d", args.CoinType)
   312  	}
   313  	reply.CurrentFee = wal.SetFee(args.Fee)
   314  	return nil
   315  }
   316  
   317  // Fee gets the fee rate for a wallet.
   318  func (r *LitRPC) GetFee(args *FeeArgs, reply *FeeReply) error {
   319  	// if cointype is 0, use the node's default coin
   320  	if args.CoinType == 0 {
   321  		args.CoinType = r.Node.DefaultCoin
   322  	}
   323  	// make sure we support that coin type
   324  	wal, ok := r.Node.SubWallet[args.CoinType]
   325  	if !ok {
   326  		return fmt.Errorf("no connnected wallet for coin type %d", args.CoinType)
   327  	}
   328  	reply.CurrentFee = wal.Fee()
   329  	return nil
   330  }
   331  
   332  // ------------------------- address
   333  type AddressArgs struct {
   334  	NumToMake uint32
   335  	CoinType  uint32
   336  }
   337  
   338  // TODO Make this contain an array of structures not a structure of arrays.
   339  type AddressReply struct {
   340  	CoinTypes       []uint32
   341  	WitAddresses    []string
   342  	LegacyAddresses []string
   343  }
   344  
   345  func (r *LitRPC) Address(args *AddressArgs, reply *AddressReply) error {
   346  	var allAdr [][20]byte
   347  	var ctypesPerAdr []uint32
   348  
   349  	// if cointype is 0, use the node's default coin
   350  	if args.CoinType == 0 {
   351  		args.CoinType = r.Node.DefaultCoin
   352  	}
   353  
   354  	// If you tell it to make 0 new addresses, it sends a list of all the old ones
   355  	// (from every wallet)
   356  	if args.NumToMake == 0 {
   357  		// this gets 20 byte addresses; need to convert them to bech32 / base58
   358  		// iterate through every wallet
   359  		for cointype, wal := range r.Node.SubWallet {
   360  			walAdr, err := wal.AdrDump()
   361  			if err != nil {
   362  				return err
   363  			}
   364  
   365  			for _, _ = range walAdr {
   366  				ctypesPerAdr = append(ctypesPerAdr, cointype)
   367  			}
   368  			allAdr = append(allAdr, walAdr...)
   369  		}
   370  	} else {
   371  		// if you have non-zero NumToMake, then cointype matters
   372  		wal, ok := r.Node.SubWallet[args.CoinType]
   373  		if !ok {
   374  			return fmt.Errorf("No wallet of cointype %d linked", args.CoinType)
   375  		}
   376  
   377  		// call NewAdr a bunch of times
   378  		remaining := args.NumToMake
   379  		for remaining > 0 {
   380  			adr, err := wal.NewAdr()
   381  			if err != nil {
   382  				return err
   383  			}
   384  			allAdr = append(allAdr, adr)
   385  			ctypesPerAdr = append(ctypesPerAdr, args.CoinType)
   386  			remaining--
   387  		}
   388  	}
   389  
   390  	reply.CoinTypes = make([]uint32, len(allAdr))
   391  	reply.WitAddresses = make([]string, len(allAdr))
   392  	reply.LegacyAddresses = make([]string, len(allAdr))
   393  
   394  	for i, a := range allAdr {
   395  
   396  		// Store the cointype
   397  		reply.CoinTypes[i] = ctypesPerAdr[i]
   398  
   399  		// convert 20 byte array to old address
   400  		param := r.Node.SubWallet[ctypesPerAdr[i]].Params()
   401  
   402  		oldadr := lnutil.OldAddressFromPKH(a, param.PubKeyHashAddrID)
   403  		reply.LegacyAddresses[i] = oldadr
   404  
   405  		// convert 20-byte PKH to a bech32 segwit v0 address
   406  		bech32adr, err := bech32.SegWitV0Encode(param.Bech32Prefix, a[:])
   407  
   408  		if err != nil {
   409  			return err
   410  		}
   411  		reply.WitAddresses[i] = bech32adr
   412  	}
   413  
   414  	return nil
   415  }
   416  
   417  // More human-readable replies
   418  func (r *LitRPC) GetAddresses(args *NoArgs, reply *AddressReply) error {
   419  
   420  	// return index
   421  	ri := 0
   422  
   423  	cts := make([]uint32, 0)
   424  	was := make([]string, 0)
   425  	las := make([]string, 0)
   426  
   427  	for cointype, wal := range r.Node.SubWallet {
   428  
   429  		walAdr, err := wal.AdrDump()
   430  		if err != nil {
   431  			panic("this should never happen, I don't think")
   432  		}
   433  
   434  		for _, pubkey := range walAdr {
   435  
   436  			param := r.Node.SubWallet[cointype].Params()
   437  			cts = append(cts, cointype)
   438  
   439  			b32, err := bech32.SegWitV0Encode(param.Bech32Prefix, pubkey[:])
   440  			if err != nil {
   441  				panic("error encoding bech32 address")
   442  			}
   443  
   444  			was = append(was, b32)
   445  			las = append(las, lnutil.OldAddressFromPKH(pubkey, param.PubKeyHashAddrID))
   446  
   447  			ri++
   448  		}
   449  
   450  	}
   451  
   452  	reply.CoinTypes = cts
   453  	reply.WitAddresses = was
   454  	reply.LegacyAddresses = las
   455  
   456  	return nil
   457  }
   458  
   459  //func oldAddressPubKeyHash(pkHash []byte, netID byte) (string, error) {
   460  //	// Check for a valid pubkey hash length.
   461  //	if len(pkHash) != ripemd160.Size {
   462  //		return "", errors.New("pkHash must be 20 bytes")
   463  //	}
   464  //	return base58.CheckEncode(pkHash, netID), nil
   465  //}
   466  
   467  type ClaimHTLCArgs struct {
   468  	R [16]byte
   469  }
   470  
   471  func (r *LitRPC) ClaimHTLC(args *ClaimHTLCArgs, reply *TxidsReply) error {
   472  	txids, err := r.Node.ClaimHTLC(args.R)
   473  	if err != nil {
   474  		return err
   475  	}
   476  
   477  	reply.Txids = make([]string, 0)
   478  	for _, txid := range txids {
   479  		reply.Txids = append(reply.Txids, fmt.Sprintf("%x", txid))
   480  	}
   481  
   482  	return nil
   483  }