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 }