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