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 }