decred.org/dcrdex@v1.0.3/client/asset/doge/doge.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 doge
     5  
     6  import (
     7  	"context"
     8  	"encoding/json"
     9  	"fmt"
    10  	"math"
    11  
    12  	"decred.org/dcrdex/client/asset"
    13  	"decred.org/dcrdex/client/asset/btc"
    14  	"decred.org/dcrdex/dex"
    15  	dexbtc "decred.org/dcrdex/dex/networks/btc"
    16  	dexdoge "decred.org/dcrdex/dex/networks/doge"
    17  	"github.com/btcsuite/btcd/chaincfg"
    18  )
    19  
    20  const (
    21  	version = 0
    22  	BipID   = 3
    23  
    24  	dustLimit = 1_000_000 // sats => 0.01 DOGE, the "soft" limit (DEFAULT_DUST_LIMIT)
    25  
    26  	minNetworkVersion = 1140700 // v1.14.7.0-a6d122013
    27  	walletTypeRPC     = "dogecoindRPC"
    28  	feeConfs          = 10
    29  )
    30  
    31  var (
    32  	fallbackFeeKey = "fallbackfee"
    33  	configOpts     = []*asset.ConfigOption{
    34  		{
    35  			Key:         "rpcuser",
    36  			DisplayName: "JSON-RPC Username",
    37  			Description: "Dogecoin's 'rpcuser' setting",
    38  		},
    39  		{
    40  			Key:         "rpcpassword",
    41  			DisplayName: "JSON-RPC Password",
    42  			Description: "Dogecoin's 'rpcpassword' setting",
    43  			NoEcho:      true,
    44  		},
    45  		{
    46  			Key:         "rpcbind",
    47  			DisplayName: "JSON-RPC Address",
    48  			Description: "<addr> or <addr>:<port> (default 'localhost')",
    49  		},
    50  		{
    51  			Key:         "rpcport",
    52  			DisplayName: "JSON-RPC Port",
    53  			Description: "Port for RPC connections (if not set in Address)",
    54  		},
    55  		{
    56  			Key:          fallbackFeeKey,
    57  			DisplayName:  "Fallback fee rate",
    58  			Description:  "Dogecoin's 'fallbackfee' rate. Units: DOGE/kB",
    59  			DefaultValue: dexdoge.DefaultFee * 1000 / 1e8,
    60  		},
    61  		{
    62  			Key:         "feeratelimit",
    63  			DisplayName: "Highest acceptable fee rate",
    64  			Description: "This is the highest network fee rate you are willing to " +
    65  				"pay on swap transactions. If feeratelimit is lower than a market's " +
    66  				"maxfeerate, you will not be able to trade on that market with this " +
    67  				"wallet.  Units: BTC/kB",
    68  			DefaultValue: dexdoge.DefaultFeeRateLimit * 1000 / 1e8,
    69  		},
    70  		{
    71  			Key:         "txsplit",
    72  			DisplayName: "Pre-split funding inputs",
    73  			Description: "When placing an order, create a \"split\" transaction to fund the order without locking more of the wallet balance than " +
    74  				"necessary. Otherwise, excess funds may be reserved to fund the order until the first swap contract is broadcast " +
    75  				"during match settlement, or the order is canceled. This an extra transaction for which network mining fees are paid. " +
    76  				"Used only for standing-type orders, e.g. limit orders without immediate time-in-force.",
    77  			IsBoolean: true,
    78  		},
    79  		{
    80  			Key:         "apifeefallback",
    81  			DisplayName: "External fee rate estimates",
    82  			Description: "Allow fee rate estimation from a block explorer API. " +
    83  				"This is useful as a fallback for SPV wallets and RPC wallets " +
    84  				"that have recently been started.",
    85  			IsBoolean:    true,
    86  			DefaultValue: true,
    87  		},
    88  	}
    89  	// WalletInfo defines some general information about a Dogecoin wallet.
    90  	WalletInfo = &asset.WalletInfo{
    91  		Name:              "Dogecoin",
    92  		SupportedVersions: []uint32{version},
    93  		UnitInfo:          dexdoge.UnitInfo,
    94  		AvailableWallets: []*asset.WalletDefinition{{
    95  			Type:              walletTypeRPC,
    96  			Tab:               "External",
    97  			Description:       "Connect to dogecoind",
    98  			DefaultConfigPath: dexbtc.SystemConfigPath("dogecoin"),
    99  			ConfigOpts:        configOpts,
   100  		}},
   101  	}
   102  )
   103  
   104  func init() {
   105  	asset.Register(BipID, &Driver{})
   106  }
   107  
   108  // Driver implements asset.Driver.
   109  type Driver struct{}
   110  
   111  // Open creates the DOGE exchange wallet. Start the wallet with its Run method.
   112  func (d *Driver) Open(cfg *asset.WalletConfig, logger dex.Logger, network dex.Network) (asset.Wallet, error) {
   113  	return NewWallet(cfg, logger, network)
   114  }
   115  
   116  // DecodeCoinID creates a human-readable representation of a coin ID for
   117  // Dogecoin.
   118  func (d *Driver) DecodeCoinID(coinID []byte) (string, error) {
   119  	// Dogecoin and Bitcoin have the same tx hash and output format.
   120  	return (&btc.Driver{}).DecodeCoinID(coinID)
   121  }
   122  
   123  // Info returns basic information about the wallet and asset.
   124  func (d *Driver) Info() *asset.WalletInfo {
   125  	return WalletInfo
   126  }
   127  
   128  // MinLotSize calculates the minimum bond size for a given fee rate that avoids
   129  // dust outputs on the swap and refund txs, assuming the maxFeeRate doesn't
   130  // change.
   131  func (d *Driver) MinLotSize(maxFeeRate uint64) uint64 {
   132  	return dustLimit + dexbtc.RedeemSwapTxSize(false)*maxFeeRate
   133  }
   134  
   135  // NewWallet is the exported constructor by which the DEX will import the
   136  // exchange wallet. The wallet will shut down when the provided context is
   137  // canceled. The configPath can be an empty string, in which case the standard
   138  // system location of the dogecoind config file is assumed.
   139  func NewWallet(cfg *asset.WalletConfig, logger dex.Logger, network dex.Network) (asset.Wallet, error) {
   140  	var params *chaincfg.Params
   141  	switch network {
   142  	case dex.Mainnet:
   143  		params = dexdoge.MainNetParams
   144  	case dex.Testnet:
   145  		params = dexdoge.TestNet4Params
   146  	case dex.Regtest:
   147  		params = dexdoge.RegressionNetParams
   148  	default:
   149  		return nil, fmt.Errorf("unknown network ID %v", network)
   150  	}
   151  
   152  	// Designate the clone ports. These will be overwritten by any explicit
   153  	// settings in the configuration file.
   154  	ports := dexbtc.NetPorts{
   155  		Mainnet: "22555",
   156  		Testnet: "44555",
   157  		Simnet:  "18332",
   158  	}
   159  	cloneCFG := &btc.BTCCloneCFG{
   160  		WalletCFG:                cfg,
   161  		MinNetworkVersion:        minNetworkVersion,
   162  		WalletInfo:               WalletInfo,
   163  		Symbol:                   "doge",
   164  		Logger:                   logger,
   165  		Network:                  network,
   166  		ChainParams:              params,
   167  		Ports:                    ports,
   168  		DefaultFallbackFee:       dexdoge.DefaultFee,
   169  		DefaultFeeRateLimit:      dexdoge.DefaultFeeRateLimit,
   170  		LegacyBalance:            true,
   171  		Segwit:                   false,
   172  		InitTxSize:               dexbtc.InitTxSize,
   173  		InitTxSizeBase:           dexbtc.InitTxSizeBase,
   174  		OmitAddressType:          true,
   175  		LegacySignTxRPC:          true,
   176  		LegacyValidateAddressRPC: true,
   177  		BooleanGetBlockRPC:       true,
   178  		SingularWallet:           true,
   179  		UnlockSpends:             true,
   180  		ConstantDustLimit:        dustLimit,
   181  		FeeEstimator:             estimateFee,
   182  		ExternalFeeEstimator:     externalFeeRate,
   183  		BlockDeserializer:        dexdoge.DeserializeBlock,
   184  		AssetID:                  BipID,
   185  	}
   186  
   187  	return btc.BTCCloneWallet(cloneCFG)
   188  }
   189  
   190  // NOTE: btc.(*baseWallet).feeRate calls the local and external fee estimators
   191  // in sequence, applying the limits configured in baseWallet.
   192  
   193  func estimateFee(ctx context.Context, cl btc.RawRequester, _ uint64) (uint64, error) {
   194  	confArg, err := json.Marshal(feeConfs)
   195  	if err != nil {
   196  		return 0, err
   197  	}
   198  	resp, err := cl.RawRequest(ctx, "estimatefee", []json.RawMessage{confArg})
   199  	if err != nil {
   200  		return 0, err
   201  	}
   202  	var feeRate float64
   203  	err = json.Unmarshal(resp, &feeRate)
   204  	if err != nil {
   205  		return 0, err
   206  	}
   207  	if feeRate <= 0 {
   208  		return 0, nil
   209  	}
   210  	// estimatefee is f#$%ed
   211  	// https://github.com/decred/dcrdex/pull/1558#discussion_r850061882
   212  	if feeRate > dexdoge.DefaultFeeRateLimit/1e5 {
   213  		return dexdoge.DefaultFee, nil
   214  	}
   215  	return uint64(math.Round(feeRate * 1e5)), nil
   216  }
   217  
   218  // DRAFT TODO: Fee rate -1 for testnet. Just use mainnet?
   219  var bitcoreFeeRate = btc.BitcoreRateFetcher("DOGE")
   220  
   221  // externalFeeRate returns a fee rate for the network. If an error is
   222  // encountered fetching the testnet fee rate, we will try to return the
   223  // mainnet fee rate.
   224  func externalFeeRate(ctx context.Context, net dex.Network) (uint64, error) {
   225  	feeRate, err := bitcoreFeeRate(ctx, net)
   226  	if err == nil || net != dex.Testnet {
   227  		return feeRate, err
   228  	}
   229  	return bitcoreFeeRate(ctx, dex.Mainnet)
   230  }