decred.org/dcrdex@v1.0.3/client/asset/driver.go (about)

     1  // This code is available on the terms of the project LICENSE.md file,
     2  // also available online at https://blueoakcouncil.org/license/1.0.0.
     3  
     4  package asset
     5  
     6  import (
     7  	"context"
     8  	"errors"
     9  	"fmt"
    10  	"sync"
    11  
    12  	"decred.org/dcrdex/dex"
    13  )
    14  
    15  // nettedToken is a Token with its available networks. Saving the valid
    16  // networks enables filtering out tokens via the package's SetNetwork.
    17  type nettedToken struct {
    18  	*Token
    19  	addrs map[dex.Network]string
    20  }
    21  
    22  var (
    23  	driversMtx sync.RWMutex
    24  	drivers    = make(map[uint32]Driver)
    25  	tokens     = make(map[uint32]*nettedToken)
    26  )
    27  
    28  // CreateWalletParams are the parameters for internal wallet creation. The
    29  // Settings provided should be the same wallet configuration settings passed to
    30  // OpenWallet.
    31  type CreateWalletParams struct {
    32  	Type     string
    33  	Seed     []byte
    34  	Pass     []byte
    35  	Birthday uint64
    36  	Settings map[string]string
    37  	DataDir  string
    38  	Net      dex.Network
    39  	Logger   dex.Logger
    40  }
    41  
    42  // Driver is the interface required of all exchange wallets.
    43  type Driver interface {
    44  	Open(*WalletConfig, dex.Logger, dex.Network) (Wallet, error)
    45  	DecodeCoinID(coinID []byte) (string, error)
    46  	Info() *WalletInfo
    47  }
    48  
    49  // Creator defines methods for Drivers that will be called to initialize seeded
    50  // wallets during CreateWallet. Only assets that provide seeded wallets need to
    51  // implement Creator.
    52  type Creator interface {
    53  	Exists(walletType, dataDir string, settings map[string]string, net dex.Network) (bool, error)
    54  	Create(*CreateWalletParams) error
    55  }
    56  
    57  func withDriver(assetID uint32, f func(Driver) error) error {
    58  	driversMtx.RLock()
    59  	drv, ok := drivers[assetID]
    60  	driversMtx.RUnlock()
    61  	if !ok {
    62  		return fmt.Errorf("asset: unknown driver asset %d", assetID)
    63  	}
    64  
    65  	return f(drv)
    66  }
    67  
    68  // Register should be called by the init function of an asset's package.
    69  func Register(assetID uint32, driver Driver) {
    70  	driversMtx.Lock()
    71  	defer driversMtx.Unlock()
    72  
    73  	if driver == nil {
    74  		panic("asset: Register driver is nil")
    75  	}
    76  	if _, dup := drivers[assetID]; dup {
    77  		panic(fmt.Sprint("asset: Register called twice for asset driver ", assetID))
    78  	}
    79  	if driver.Info().UnitInfo.Conventional.ConversionFactor == 0 {
    80  		panic(fmt.Sprint("asset: Registered driver doesn't have a conventional conversion factor set in the wallet info ", assetID))
    81  	}
    82  	drivers[assetID] = driver
    83  }
    84  
    85  // RegisterToken should be called to register tokens. If no nets are specified
    86  // the token will be registered for all networks. The user must invoke
    87  // SetNetwork to enable net-based filtering of package function output.
    88  func RegisterToken(tokenID uint32, token *dex.Token, walletDef *WalletDefinition, addrs map[dex.Network]string) {
    89  	driversMtx.Lock()
    90  	defer driversMtx.Unlock()
    91  	if _, exists := tokens[tokenID]; exists {
    92  		panic(fmt.Sprintf("token %d already exists", tokenID))
    93  	}
    94  	_, exists := drivers[token.ParentID]
    95  	if !exists {
    96  		panic(fmt.Sprintf("token %d's parent asset %d isn't registered", tokenID, token.ParentID))
    97  	}
    98  	tokens[tokenID] = &nettedToken{
    99  		Token: &Token{
   100  			Token:      token,
   101  			Definition: walletDef,
   102  			// ContractAddress specified in SetNetwork.
   103  		},
   104  		addrs: addrs,
   105  	}
   106  }
   107  
   108  // WalletExists will be true if the specified wallet exists.
   109  func WalletExists(assetID uint32, walletType, dataDir string, settings map[string]string, net dex.Network) (exists bool, err error) {
   110  	return exists, withDriver(assetID, func(drv Driver) error {
   111  		creator, is := drv.(Creator)
   112  		if !is {
   113  			return fmt.Errorf("driver has no Exists method")
   114  		}
   115  		exists, err = creator.Exists(walletType, dataDir, settings, net)
   116  		return err
   117  	})
   118  }
   119  
   120  // CreateWallet creates a new wallet. Only use Create for seeded wallet types.
   121  func CreateWallet(assetID uint32, seedParams *CreateWalletParams) error {
   122  	return withDriver(assetID, func(drv Driver) error {
   123  		creator, is := drv.(Creator)
   124  		if !is {
   125  			return fmt.Errorf("driver has no Create method")
   126  		}
   127  		return creator.Create(seedParams)
   128  	})
   129  }
   130  
   131  // OpenWallet sets up the asset, returning the exchange wallet.
   132  func OpenWallet(assetID uint32, cfg *WalletConfig, logger dex.Logger, net dex.Network) (w Wallet, err error) {
   133  	return w, withDriver(assetID, func(drv Driver) error {
   134  		w, err = drv.Open(cfg, logger, net)
   135  		return err
   136  	})
   137  }
   138  
   139  // DecodeCoinID creates a human-readable representation of a coin ID for a named
   140  // asset with a corresponding driver registered with this package. For now,
   141  // token id decoding is deferred to the parent asset.
   142  func DecodeCoinID(assetID uint32, coinID []byte) (cid string, err error) {
   143  	driversMtx.RLock()
   144  	defer driversMtx.RUnlock()
   145  	drv, ok := drivers[assetID]
   146  	if ok {
   147  		return drv.DecodeCoinID(coinID)
   148  	}
   149  	tkn, ok := tokens[assetID]
   150  	if !ok {
   151  		return "", fmt.Errorf("no driver or token info for asset %d (%s)", assetID, dex.BipIDSymbol(assetID))
   152  	}
   153  	drv, ok = drivers[tkn.ParentID]
   154  	if ok {
   155  		return drv.DecodeCoinID(coinID)
   156  	}
   157  	return "", fmt.Errorf("no %d (%s) parent asset driver for token %d (%s)",
   158  		tkn.ParentID, dex.BipIDSymbol(tkn.ParentID), assetID, dex.BipIDSymbol(assetID))
   159  }
   160  
   161  // A registered asset is information about a supported asset.
   162  type RegisteredAsset struct {
   163  	ID     uint32
   164  	Symbol string
   165  	Info   *WalletInfo
   166  	Tokens map[uint32]*Token
   167  }
   168  
   169  // Assets returns a list of information about supported assets.
   170  func Assets() map[uint32]*RegisteredAsset {
   171  	driversMtx.RLock()
   172  	defer driversMtx.RUnlock()
   173  	assets := make(map[uint32]*RegisteredAsset, len(drivers))
   174  	for assetID, driver := range drivers {
   175  		assets[assetID] = &RegisteredAsset{
   176  			ID:     assetID,
   177  			Symbol: dex.BipIDSymbol(assetID),
   178  			Info:   driver.Info(),
   179  		}
   180  	}
   181  
   182  	for tokenID, token := range tokens {
   183  		parent, found := assets[token.ParentID]
   184  		if !found {
   185  			// should be impossible.
   186  			fmt.Println("parentless token", tokenID, token.Name)
   187  			continue
   188  		}
   189  		if parent.Tokens == nil {
   190  			parent.Tokens = make(map[uint32]*Token, 1)
   191  		}
   192  		parent.Tokens[tokenID] = token.Token
   193  	}
   194  	return assets
   195  }
   196  
   197  // Asset gets the RegisteredAsset for the specified asset ID. Asset is for
   198  // base chain assets, not tokens.
   199  func Asset(assetID uint32) *RegisteredAsset {
   200  	driversMtx.RLock()
   201  	defer driversMtx.RUnlock()
   202  	drv := drivers[assetID]
   203  	if drv == nil {
   204  		return nil
   205  	}
   206  	ra := &RegisteredAsset{
   207  		ID:     assetID,
   208  		Symbol: dex.BipIDSymbol(assetID),
   209  		Info:   drv.Info(),
   210  	}
   211  	for tokenID, token := range tokens {
   212  		if token.ParentID == assetID {
   213  			if ra.Tokens == nil {
   214  				ra.Tokens = make(map[uint32]*Token, 1)
   215  			}
   216  			ra.Tokens[tokenID] = token.Token
   217  		}
   218  	}
   219  	return ra
   220  }
   221  
   222  // TokenInfo returns *Token for a registered token, or nil if the token is
   223  // unknown.
   224  func TokenInfo(assetID uint32) *Token {
   225  	driversMtx.RLock()
   226  	defer driversMtx.RUnlock()
   227  	nt := tokens[assetID]
   228  	if nt == nil {
   229  		return nil
   230  	}
   231  	return nt.Token
   232  }
   233  
   234  // Info returns the WalletInfo for the specified asset, if supported. Info only
   235  // returns WalletInfo for base chain assets, not tokens.
   236  func Info(assetID uint32) (*WalletInfo, error) {
   237  	driversMtx.RLock()
   238  	drv, ok := drivers[assetID]
   239  	driversMtx.RUnlock()
   240  	if !ok {
   241  		return nil, fmt.Errorf("asset: unsupported asset %d", assetID)
   242  	}
   243  	return drv.Info(), nil
   244  }
   245  
   246  // UnitInfo returns the dex.UnitInfo for the asset or token.
   247  func UnitInfo(assetID uint32) (dex.UnitInfo, error) {
   248  	driversMtx.RLock()
   249  	defer driversMtx.RUnlock()
   250  	drv, ok := drivers[assetID]
   251  	if ok {
   252  		return drv.Info().UnitInfo, nil
   253  	}
   254  	token, ok := tokens[assetID]
   255  	if ok {
   256  		return token.UnitInfo, nil
   257  	}
   258  	return dex.UnitInfo{}, fmt.Errorf("asset: unsupported asset %d", assetID)
   259  }
   260  
   261  // SetNetwork will filter registered assets for those available on the specified
   262  // network. SetNetwork need only be called once during initialization.
   263  func SetNetwork(net dex.Network) {
   264  	for assetID, nt := range tokens {
   265  		addr, exists := nt.addrs[net]
   266  		if !exists {
   267  			delete(tokens, assetID)
   268  			continue
   269  		}
   270  		nt.Token.ContractAddress = addr
   271  	}
   272  }
   273  
   274  // WalletDef gets the registered WalletDefinition for the asset and
   275  // wallet type.
   276  func WalletDef(assetID uint32, walletType string) (*WalletDefinition, error) {
   277  	token := TokenInfo(assetID)
   278  	if token != nil {
   279  		return token.Definition, nil
   280  	}
   281  	winfo, err := Info(assetID)
   282  	if err != nil {
   283  		return nil, fmt.Errorf("Info error: %w", err)
   284  	}
   285  	if walletType == "" {
   286  		if len(winfo.AvailableWallets) <= winfo.LegacyWalletIndex {
   287  			return nil, fmt.Errorf("legacy wallet index out of range")
   288  		}
   289  		return winfo.AvailableWallets[winfo.LegacyWalletIndex], nil
   290  	}
   291  	var wd *WalletDefinition
   292  	for _, def := range winfo.AvailableWallets {
   293  		if def.Type == walletType {
   294  			wd = def
   295  		}
   296  	}
   297  	if wd == nil {
   298  		return nil, fmt.Errorf("could not find wallet definition for asset %s, type %q", dex.BipIDSymbol(assetID), walletType)
   299  	}
   300  	return wd, nil
   301  }
   302  
   303  // MinimumLotSize returns the minimimum lot size for a registered asset.
   304  func MinimumLotSize(assetID uint32, maxFeeRate uint64) (minLotSize uint64, found bool) {
   305  	baseChainID := assetID
   306  	if token, is := tokens[assetID]; is {
   307  		baseChainID = token.ParentID
   308  	}
   309  	drv, found := drivers[baseChainID]
   310  	if !found {
   311  		return 0, false
   312  	}
   313  	m, is := drv.(interface {
   314  		MinLotSize(maxFeeRate uint64) uint64
   315  	})
   316  	if !is {
   317  		return 1, true
   318  	}
   319  	return m.MinLotSize(maxFeeRate), true
   320  }
   321  
   322  func FormatAtoms(assetID uint32, atoms uint64) string {
   323  	if ui, err := UnitInfo(assetID); err == nil {
   324  		return ui.FormatAtoms(atoms)
   325  	}
   326  	return "<unknown asset>"
   327  }
   328  
   329  type spvWithdrawFunc func(ctx context.Context, walletPW []byte, recipient, dataDir string, net dex.Network, log dex.Logger) ([]byte, error)
   330  
   331  var spvRecovererFuncs = make(map[uint32]spvWithdrawFunc)
   332  
   333  // RegisterSPVWithdrawFunc registers the function to genreate a withdraw
   334  // transaction that spends funds from a deprecated SPV wallet.
   335  func RegisterSPVWithdrawFunc(assetID uint32, f spvWithdrawFunc) {
   336  	spvRecovererFuncs[assetID] = f
   337  }
   338  
   339  // SPVWithdrawTx generates a transaction that spends all funds from a deprecated
   340  // spv wallet.
   341  func SPVWithdrawTx(ctx context.Context, assetID uint32, walletPW []byte, recipient, dataDir string, net dex.Network, log dex.Logger) ([]byte, error) {
   342  	f, found := spvRecovererFuncs[assetID]
   343  	if !found {
   344  		return nil, errors.New("no withdraw function")
   345  	}
   346  	return f(ctx, walletPW, recipient, dataDir, net, log)
   347  }