diff --git a/client/asset/bch/bch.go b/client/asset/bch/bch.go
index 502653d831..2938a5a18d 100644
--- a/client/asset/bch/bch.go
+++ b/client/asset/bch/bch.go
@@ -89,6 +89,7 @@ var (
rpcWalletDefinition,
// electrumWalletDefinition, // getinfo RPC needs backport: https://github.com/Electron-Cash/Electron-Cash/pull/2399
},
+ BlockchainClass: asset.BlockchainClassUTXO,
}
externalFeeRate = btc.BitcoreRateFetcher("BCH")
diff --git a/client/asset/btc/btc.go b/client/asset/btc/btc.go
index 0e09d5cf82..7937319c4a 100644
--- a/client/asset/btc/btc.go
+++ b/client/asset/btc/btc.go
@@ -216,6 +216,7 @@ var (
electrumWalletDefinition,
},
LegacyWalletIndex: 1,
+ BlockchainClass: asset.BlockchainClassUTXO,
}
)
diff --git a/client/asset/dash/dash.go b/client/asset/dash/dash.go
index 508abbd9d9..bc8d9676de 100644
--- a/client/asset/dash/dash.go
+++ b/client/asset/dash/dash.go
@@ -74,6 +74,7 @@ var (
ConfigOpts: configOpts,
},
},
+ BlockchainClass: asset.BlockchainClassUTXO,
}
)
diff --git a/client/asset/dcr/dcr.go b/client/asset/dcr/dcr.go
index ff557bdffd..aa95f8fc98 100644
--- a/client/asset/dcr/dcr.go
+++ b/client/asset/dcr/dcr.go
@@ -289,6 +289,7 @@ var (
MultiFundingOpts: multiFundingOpts,
},
},
+ BlockchainClass: asset.BlockchainClassUTXO,
}
swapFeeBumpKey = "swapfeebump"
splitKey = "swapsplit"
diff --git a/client/asset/dgb/dgb.go b/client/asset/dgb/dgb.go
index 962fa5ce0c..8290c107b3 100644
--- a/client/asset/dgb/dgb.go
+++ b/client/asset/dgb/dgb.go
@@ -78,6 +78,7 @@ var (
DefaultConfigPath: dexbtc.SystemConfigPath("digibyte"),
ConfigOpts: configOpts,
}},
+ BlockchainClass: asset.BlockchainClassUTXO,
}
)
diff --git a/client/asset/doge/doge.go b/client/asset/doge/doge.go
index 97ee04ff33..175ce8c11c 100644
--- a/client/asset/doge/doge.go
+++ b/client/asset/doge/doge.go
@@ -98,6 +98,7 @@ var (
DefaultConfigPath: dexbtc.SystemConfigPath("dogecoin"),
ConfigOpts: configOpts,
}},
+ BlockchainClass: asset.BlockchainClassUTXO,
}
)
diff --git a/client/asset/eth/eth.go b/client/asset/eth/eth.go
index 8eef685728..a29c1bd579 100644
--- a/client/asset/eth/eth.go
+++ b/client/asset/eth/eth.go
@@ -182,7 +182,7 @@ var (
// MaxSwapsInTx and MaxRedeemsInTx are set in (Wallet).Info, since
// the value cannot be known until we connect and get network info.
},
- IsAccountBased: true,
+ BlockchainClass: asset.BlockchainClassEVM,
}
// unlimitedAllowance is the maximum supported allowance for an erc20
@@ -660,7 +660,7 @@ func CreateEVMWallet(chainID int64, createWalletParams *asset.CreateWalletParams
func newWallet(assetCFG *asset.WalletConfig, logger dex.Logger, net dex.Network) (w *ETHWallet, err error) {
chainCfg, err := ChainConfig(net)
if err != nil {
- return nil, fmt.Errorf("failed to locate Ethereum genesis configuration for network %s", net)
+ return nil, fmt.Errorf("failed to locate Ethereum genesis configuration for network %s: %v", net, err)
}
comp, err := NetworkCompatibilityData(net)
if err != nil {
@@ -1284,6 +1284,7 @@ func (w *ETHWallet) OpenTokenWallet(tokenCfg *asset.TokenConfig) (asset.Wallet,
Name: token.Name,
SupportedVersions: w.wi.SupportedVersions,
UnitInfo: token.UnitInfo,
+ BlockchainClass: asset.BlockchainClassEVM,
},
pendingTxCheckBal: new(big.Int),
}
diff --git a/client/asset/firo/firo.go b/client/asset/firo/firo.go
index b2349da9f0..383836c8ff 100644
--- a/client/asset/firo/firo.go
+++ b/client/asset/firo/firo.go
@@ -84,6 +84,7 @@ var (
ConfigOpts: append(btc.ElectrumConfigOpts, btc.CommonConfigOpts("FIRO", true)...),
},
},
+ BlockchainClass: asset.BlockchainClassUTXO,
}
)
diff --git a/client/asset/interface.go b/client/asset/interface.go
index 68af08d7f4..00b8b531ea 100644
--- a/client/asset/interface.go
+++ b/client/asset/interface.go
@@ -304,6 +304,17 @@ type Token struct {
ContractAddress string `json:"contractAddress"` // Set in SetNetwork
}
+type BlockchainClass string
+
+const (
+ BlockchainClassUTXO BlockchainClass = "UTXO"
+ BlockchainClassEVM BlockchainClass = "EVM"
+)
+
+func (c BlockchainClass) IsEVM() bool {
+ return c == BlockchainClassEVM
+}
+
// WalletInfo is auxiliary information about an ExchangeWallet.
type WalletInfo struct {
// Name is the display name for the currency, e.g. "Decred"
@@ -331,10 +342,8 @@ type WalletInfo struct {
// MaxRedeemsInTx is the max amount of redemptions that this wallet can do
// in a single transaction.
MaxRedeemsInTx uint64
- // IsAccountBased should be set to true for account-based (EVM) assets, so
- // that a common seed will be generated and wallets will generate the
- // same address.
- IsAccountBased bool
+ // BlockchainClass is the type of the wallet's blockchain.
+ BlockchainClass BlockchainClass
}
// ConfigOption is a wallet configuration option.
diff --git a/client/asset/ltc/ltc.go b/client/asset/ltc/ltc.go
index 385f1a21f6..75e0ea4d07 100644
--- a/client/asset/ltc/ltc.go
+++ b/client/asset/ltc/ltc.go
@@ -79,6 +79,7 @@ var (
rpcWalletDefinition,
electrumWalletDefinition,
},
+ BlockchainClass: asset.BlockchainClassUTXO,
}
)
diff --git a/client/asset/polygon/polygon.go b/client/asset/polygon/polygon.go
index 6e65cf8dff..6ba83da800 100644
--- a/client/asset/polygon/polygon.go
+++ b/client/asset/polygon/polygon.go
@@ -87,7 +87,7 @@ var (
// MaxSwapsInTx and MaxRedeemsInTx are set in (Wallet).Info, since
// the value cannot be known until we connect and get network info.
},
- IsAccountBased: true,
+ BlockchainClass: asset.BlockchainClassEVM,
}
)
diff --git a/client/asset/zcl/zcl.go b/client/asset/zcl/zcl.go
index c7cb75c7d0..2d00548238 100644
--- a/client/asset/zcl/zcl.go
+++ b/client/asset/zcl/zcl.go
@@ -99,6 +99,7 @@ var (
ConfigOpts: configOpts,
NoAuth: true,
}},
+ BlockchainClass: asset.BlockchainClassUTXO,
}
)
diff --git a/client/asset/zec/zec.go b/client/asset/zec/zec.go
index bd68bb57d1..ff714bf086 100644
--- a/client/asset/zec/zec.go
+++ b/client/asset/zec/zec.go
@@ -139,6 +139,7 @@ var (
ConfigOpts: configOpts,
NoAuth: true,
}},
+ BlockchainClass: asset.BlockchainClassUTXO,
}
feeReservesPerLot = dexzec.TxFeesZIP317(dexbtc.RedeemP2PKHInputSize+1, 2*dexbtc.P2PKHOutputSize+1, 0, 0, 0, 0)
diff --git a/client/core/core.go b/client/core/core.go
index f3572b99d2..499fa444de 100644
--- a/client/core/core.go
+++ b/client/core/core.go
@@ -1529,9 +1529,6 @@ type Core struct {
reFiat chan struct{}
- pendingWalletsMtx sync.RWMutex
- pendingWallets map[uint32]bool
-
notes chan asset.WalletNotification
pokesCache *pokesCache
@@ -1672,7 +1669,6 @@ func New(cfg *Config) (*Core, error) {
fiatRateSources: make(map[string]*commonRateSource),
reFiat: make(chan struct{}, 1),
- pendingWallets: make(map[uint32]bool),
notes: make(chan asset.WalletNotification, 128),
requestedActions: make(map[string]*asset.ActionRequiredNote),
@@ -2020,11 +2016,41 @@ func (c *Core) dexConnections() []*dexConnection {
// wallet gets the wallet for the specified asset ID in a thread-safe way.
func (c *Core) wallet(assetID uint32) (*xcWallet, bool) {
c.walletMtx.RLock()
- defer c.walletMtx.RUnlock()
w, found := c.wallets[assetID]
+ c.walletMtx.RUnlock()
return w, found
}
+func (c *Core) initializeTokenWallet(tokenID uint32, tkn *asset.Token) error {
+ if _, found := c.wallet(tkn.ParentID); !found {
+ return fmt.Errorf("no parent wallet %d for token %s", tkn.ParentID, tkn.Name)
+ }
+ dbWallet, err := c.createTokenDBWallet(tokenID, tkn, &WalletForm{
+ AssetID: tokenID,
+ Config: make(map[string]string),
+ Type: tkn.Definition.Type,
+ })
+ if err != nil {
+ return fmt.Errorf("error creating %s token wallet with existing %s parent wallet: %w",
+ dex.BipIDSymbol(tokenID), dex.BipIDSymbol(tkn.ParentID), err)
+ }
+ w, err := c.loadXCWallet(dbWallet)
+ if err != nil {
+ return fmt.Errorf("error loading newly created %s token wallet: %w", tkn.Name, err)
+ }
+ bals := &WalletBalance{Balance: &db.Balance{Balance: asset.Balance{Other: make(map[asset.BalanceCategory]asset.CustomBalance)}}}
+ w.setBalance(bals) // update xcWallet's WalletBalance
+ dbWallet.Balance = bals.Balance
+ // Store the wallet in the database.
+ err = c.db.UpdateWallet(dbWallet)
+ if err != nil {
+ return fmt.Errorf("error storing new token wallet credentials: %w", err)
+ }
+ c.log.Infof("Token wallet for %s automatically created", dex.BipIDSymbol(tokenID))
+ c.updateWallet(tokenID, w)
+ return nil
+}
+
// encryptionKey retrieves the application encryption key. The password is used
// to recreate the outer key/crypter, which is then used to decode and recreate
// the inner key/crypter.
@@ -2449,28 +2475,6 @@ func (c *Core) SupportedAssets() map[uint32]*SupportedAsset {
return c.assetMap()
}
-func (c *Core) walletCreationPending(tokenID uint32) bool {
- c.pendingWalletsMtx.RLock()
- defer c.pendingWalletsMtx.RUnlock()
- return c.pendingWallets[tokenID]
-}
-
-func (c *Core) setWalletCreationPending(tokenID uint32) error {
- c.pendingWalletsMtx.Lock()
- defer c.pendingWalletsMtx.Unlock()
- if c.pendingWallets[tokenID] {
- return fmt.Errorf("creation already pending for %s", unbip(tokenID))
- }
- c.pendingWallets[tokenID] = true
- return nil
-}
-
-func (c *Core) setWalletCreationComplete(tokenID uint32) {
- c.pendingWalletsMtx.Lock()
- delete(c.pendingWallets, tokenID)
- c.pendingWalletsMtx.Unlock()
-}
-
// assetMap returns a map of asset information for supported assets.
func (c *Core) assetMap() map[uint32]*SupportedAsset {
supported := asset.Assets()
@@ -2498,13 +2502,12 @@ func (c *Core) assetMap() map[uint32]*SupportedAsset {
wallet = w.state()
}
assets[tokenID] = &SupportedAsset{
- ID: tokenID,
- Symbol: dex.BipIDSymbol(tokenID),
- Wallet: wallet,
- Token: token,
- Name: token.Name,
- UnitInfo: token.UnitInfo,
- WalletCreationPending: c.walletCreationPending(tokenID),
+ ID: tokenID,
+ Symbol: dex.BipIDSymbol(tokenID),
+ Wallet: wallet,
+ Token: token,
+ Name: token.Name,
+ UnitInfo: token.UnitInfo,
}
}
}
@@ -2535,13 +2538,12 @@ func (c *Core) asset(assetID uint32) *SupportedAsset {
}
return &SupportedAsset{
- ID: assetID,
- Symbol: dex.BipIDSymbol(assetID),
- Wallet: wallet,
- Token: token,
- Name: token.Name,
- UnitInfo: token.UnitInfo,
- WalletCreationPending: c.walletCreationPending(assetID),
+ ID: assetID,
+ Symbol: dex.BipIDSymbol(assetID),
+ Wallet: wallet,
+ Token: token,
+ Name: token.Name,
+ UnitInfo: token.UnitInfo,
}
}
@@ -2571,132 +2573,42 @@ func (c *Core) requestedActionsList() []*asset.ActionRequiredNote {
// CreateWallet creates a new exchange wallet.
func (c *Core) CreateWallet(appPW, walletPW []byte, form *WalletForm) error {
- assetID := form.AssetID
- symbol := unbip(assetID)
- _, exists := c.wallet(assetID)
- if exists {
- return fmt.Errorf("%s wallet already exists", symbol)
- }
-
crypter, err := c.encryptionKey(appPW)
if err != nil {
return err
}
-
- var creationQueued bool
- defer func() {
- if !creationQueued {
- crypter.Close()
- }
- }()
-
- // If this isn't a token, easy route.
- token := asset.TokenInfo(assetID)
- if token == nil {
- _, err = c.createWalletOrToken(crypter, walletPW, form)
- return err
- }
-
- // Prevent two different tokens from trying to create the parent simultaneously.
- if err = c.setWalletCreationPending(token.ParentID); err != nil {
- return err
- }
- defer c.setWalletCreationComplete(token.ParentID)
-
- // If the parent already exists, easy route.
- _, found := c.wallet(token.ParentID)
- if found {
- _, err = c.createWalletOrToken(crypter, walletPW, form)
- return err
- }
-
- // Double-registration mode. The parent wallet will be created
- // synchronously, then a goroutine is launched to wait for the parent to
- // sync before creating the token wallet. The caller can get information
- // about the asynchronous creation from WalletCreationNote notifications.
-
- // First check that they configured the parent asset.
- if form.ParentForm == nil {
- return fmt.Errorf("no parent wallet %d for token %d (%s), and no parent asset configuration provided",
- token.ParentID, assetID, unbip(assetID))
- }
- if form.ParentForm.AssetID != token.ParentID {
- return fmt.Errorf("parent form asset ID %d is not expected value %d",
- form.ParentForm.AssetID, token.ParentID)
- }
-
- // Create the parent synchronously.
- parentWallet, err := c.createWalletOrToken(crypter, walletPW, form.ParentForm)
- if err != nil {
- return fmt.Errorf("error creating parent wallet: %v", err)
- }
-
- if err = c.setWalletCreationPending(assetID); err != nil {
- return err
- }
-
- // Start a goroutine to wait until the parent wallet is synced, and then
- // begin creation of the token wallet.
- c.wg.Add(1)
-
- c.notify(newWalletCreationNote(TopicCreationQueued, "", "", db.Data, assetID))
-
- go func() {
- defer c.wg.Done()
- defer c.setWalletCreationComplete(assetID)
- defer crypter.Close()
-
- for {
- parentWallet.mtx.RLock()
- synced := parentWallet.syncStatus.Synced
- parentWallet.mtx.RUnlock()
- if synced {
- break
- }
- select {
- case <-c.ctx.Done():
- return
- case <-time.After(time.Second):
- }
- }
- // If there was a walletPW provided, it was for the parent wallet, so
- // use nil here.
- if _, err := c.createWalletOrToken(crypter, nil, form); err != nil {
- c.log.Errorf("failed to create token wallet: %v", err)
- subject, details := c.formatDetails(TopicQueuedCreationFailed, unbip(token.ParentID), symbol)
- c.notify(newWalletCreationNote(TopicQueuedCreationFailed, subject, details, db.ErrorLevel, assetID))
- } else {
- c.notify(newWalletCreationNote(TopicQueuedCreationSuccess, "", "", db.Data, assetID))
- }
- }()
- creationQueued = true
- return nil
+ return c.createWallet(crypter, walletPW, form)
}
-func (c *Core) createWalletOrToken(crypter encrypt.Crypter, walletPW []byte, form *WalletForm) (wallet *xcWallet, err error) {
+func (c *Core) createWallet(crypter encrypt.Crypter, walletPW []byte, form *WalletForm) (err error) {
assetID := form.AssetID
symbol := unbip(assetID)
+ _, exists := c.wallet(assetID)
+ if exists {
+ return fmt.Errorf("%s wallet already exists", symbol)
+ }
+
token := asset.TokenInfo(assetID)
var dbWallet *db.Wallet
- if token != nil {
- dbWallet, err = c.createTokenWallet(assetID, token, form)
+ if token == nil {
+ dbWallet, err = c.createDBWallet(crypter, form, walletPW)
} else {
- dbWallet, err = c.createWallet(crypter, walletPW, assetID, form)
+ dbWallet, err = c.createTokenDBWallet(assetID, token, form)
}
if err != nil {
- return nil, err
+ return err
}
- wallet, err = c.loadWallet(dbWallet)
+ wallet, err := c.loadXCWallet(dbWallet)
if err != nil {
- return nil, fmt.Errorf("error loading wallet for %d -> %s: %w", assetID, symbol, err)
+ return fmt.Errorf("error loading wallet for %d -> %s: %w", assetID, symbol, err)
}
// Block PeersChange until we know this wallet is ready.
atomic.StoreUint32(wallet.broadcasting, 0)
dbWallet.Address, err = c.connectWallet(wallet)
if err != nil {
- return nil, err
+ return err
}
if c.cfg.UnlockCoinsOnLogin {
@@ -2705,16 +2617,16 @@ func (c *Core) createWalletOrToken(crypter encrypt.Crypter, walletPW []byte, for
}
}
- initErr := func(s string, a ...any) (*xcWallet, error) {
+ initErr := func(s string, a ...any) error {
_ = wallet.Lock(2 * time.Second) // just try, but don't confuse the user with an error
wallet.Disconnect()
- return nil, fmt.Errorf(s, a...)
+ return fmt.Errorf(s, a...)
}
err = c.unlockWallet(crypter, wallet) // no-op if !wallet.Wallet.Locked() && len(encPW) == 0
if err != nil {
wallet.Disconnect()
- return nil, fmt.Errorf("%s wallet authentication error: %w", symbol, err)
+ return fmt.Errorf("%s wallet authentication error: %w", symbol, err)
}
balances, err := c.walletBalance(wallet)
@@ -2742,11 +2654,25 @@ func (c *Core) createWalletOrToken(crypter encrypt.Crypter, walletPW []byte, for
c.notify(newWalletStateNote(wallet.state()))
c.walletCheckAndNotify(wallet)
- return wallet, nil
+ // Create all token wallets
+ if token == nil {
+ for tokenID, tkn := range asset.Asset(assetID).Tokens {
+ form := &WalletForm{
+ AssetID: tokenID,
+ Config: make(map[string]string),
+ Type: tkn.Definition.Type,
+ }
+ if err := c.createWallet(crypter, nil, form); err != nil {
+ c.log.Errorf("Error creating token %s wallet for parent %s", tkn.Name, symbol)
+ }
+ }
+ }
+
+ return nil
}
-func (c *Core) createWallet(crypter encrypt.Crypter, walletPW []byte, assetID uint32, form *WalletForm) (*db.Wallet, error) {
- walletDef, err := asset.WalletDef(assetID, form.Type)
+func (c *Core) createDBWallet(crypter encrypt.Crypter, form *WalletForm, walletPW []byte) (*db.Wallet, error) {
+ walletDef, err := asset.WalletDef(form.AssetID, form.Type)
if err != nil {
return nil, newError(assetSupportErr, "asset.WalletDef error: %w", err)
}
@@ -2776,7 +2702,7 @@ func (c *Core) createWallet(crypter encrypt.Crypter, walletPW []byte, assetID ui
if len(walletPW) > 0 {
return nil, errors.New("external password incompatible with seeded wallet")
}
- walletPW, err = c.createSeededWallet(assetID, crypter, form)
+ walletPW, err = c.createSeededWallet(form.AssetID, crypter, form)
if err != nil {
return nil, err
}
@@ -2792,14 +2718,14 @@ func (c *Core) createWallet(crypter encrypt.Crypter, walletPW []byte, assetID ui
return &db.Wallet{
Type: walletDef.Type,
- AssetID: assetID,
+ AssetID: form.AssetID,
Settings: form.Config,
EncryptedPW: encPW,
// Balance and Address are set after connect.
}, nil
}
-func (c *Core) createTokenWallet(tokenID uint32, token *asset.Token, form *WalletForm) (*db.Wallet, error) {
+func (c *Core) createTokenDBWallet(tokenID uint32, token *asset.Token, form *WalletForm) (*db.Wallet, error) {
wallet, found := c.wallet(token.ParentID)
if !found {
return nil, fmt.Errorf("no parent wallet %d for token %d (%s)", token.ParentID, tokenID, unbip(tokenID))
@@ -2889,14 +2815,14 @@ func (c *Core) assetSeedAndPass(assetID uint32, crypter encrypt.Crypter) (seed,
func AssetSeedAndPass(assetID uint32, appSeed []byte) ([]byte, []byte) {
const accountBasedSeedAssetID = 60 // ETH
seedAssetID := assetID
- if ai, _ := asset.Info(assetID); ai != nil && ai.IsAccountBased {
+ if ai, _ := asset.Info(assetID); ai != nil && ai.BlockchainClass.IsEVM() {
seedAssetID = accountBasedSeedAssetID
}
// Tokens asset IDs shouldn't be passed in, but if they are, return the seed
// for the parent ID.
if tkn := asset.TokenInfo(assetID); tkn != nil {
if ai, _ := asset.Info(tkn.ParentID); ai != nil {
- if ai.IsAccountBased {
+ if ai.BlockchainClass.IsEVM() {
seedAssetID = accountBasedSeedAssetID
}
}
@@ -2924,7 +2850,7 @@ func (c *Core) assetDataBackupDirectory(assetID uint32) string {
// loadWallet uses the data from the database to construct a new exchange
// wallet. The returned wallet is running but not connected.
-func (c *Core) loadWallet(dbWallet *db.Wallet) (*xcWallet, error) {
+func (c *Core) loadXCWallet(dbWallet *db.Wallet) (*xcWallet, error) {
var parent *xcWallet
assetID := dbWallet.AssetID
@@ -2969,7 +2895,6 @@ func (c *Core) loadWallet(dbWallet *db.Wallet) (*xcWallet, error) {
var w asset.Wallet
var err error
if token == nil {
-
walletCfg := &asset.WalletConfig{
Type: dbWallet.Type,
Settings: dbWallet.Settings,
@@ -3351,7 +3276,7 @@ func (c *Core) RecoverWallet(assetID uint32, appPW []byte, force bool) error {
return fmt.Errorf("error creating wallet: %w", err)
}
- newWallet, err := c.loadWallet(dbWallet)
+ newWallet, err := c.loadXCWallet(dbWallet)
if err != nil {
return newError(walletErr, "error loading wallet for %d -> %s: %w",
assetID, unbip(assetID), err)
@@ -3716,7 +3641,7 @@ func (c *Core) ReconfigureWallet(appPW, newWalletPW []byte, form *WalletForm) er
}
// Reload the wallet with the new settings.
- wallet, err := c.loadWallet(dbWallet)
+ wallet, err := c.loadXCWallet(dbWallet)
if err != nil {
return newError(walletErr, "error loading wallet for %d -> %s: %w",
assetID, unbip(assetID), err)
@@ -3819,7 +3744,7 @@ func (c *Core) ReconfigureWallet(appPW, newWalletPW []byte, form *WalletForm) er
continue
}
tokenWallet.Disconnect()
- tokenWallet, err = c.loadWallet(tokenDBWallet)
+ tokenWallet, err = c.loadXCWallet(tokenDBWallet)
if err != nil {
c.log.Errorf("Error loading wallet for token %s: %w", unbip(tokenID), err)
continue
@@ -7186,13 +7111,15 @@ func (c *Core) initialize() error {
wg.Wait()
c.log.Infof("Connected to %d of %d DEX servers", liveConns, len(accts))
+ existingTokenWallets := make(map[uint32]bool)
for _, dbWallet := range dbWallets {
- if asset.Asset(dbWallet.AssetID) == nil && asset.TokenInfo(dbWallet.AssetID) == nil {
+ tkn := asset.TokenInfo(dbWallet.AssetID)
+ if asset.Asset(dbWallet.AssetID) == nil && tkn == nil {
c.log.Infof("Wallet for asset %s no longer supported", dex.BipIDSymbol(dbWallet.AssetID))
continue
}
assetID := dbWallet.AssetID
- wallet, err := c.loadWallet(dbWallet)
+ wallet, err := c.loadXCWallet(dbWallet)
if err != nil {
c.log.Errorf("error loading %d -> %s wallet: %v", assetID, unbip(assetID), err)
continue
@@ -7200,6 +7127,27 @@ func (c *Core) initialize() error {
// Wallet is loaded from the DB, but not yet connected.
c.log.Tracef("Loaded %s wallet configuration.", unbip(assetID))
c.updateWallet(assetID, wallet)
+
+ if tkn != nil {
+ existingTokenWallets[dbWallet.AssetID] = true
+ }
+ }
+
+ // Check for missing token wallets
+ for _, dbWallet := range dbWallets {
+ a := asset.Asset(dbWallet.AssetID)
+ if a == nil {
+ continue
+ }
+ for tokenID, tkn := range a.Tokens {
+ if existingTokenWallets[tokenID] {
+ continue
+ }
+ // Let's create the missing token wallet
+ if err := c.initializeTokenWallet(tokenID, tkn); err != nil {
+ c.log.Errorf("Couldn't create missing token wallet: %v", err)
+ }
+ }
}
// Check DB for active orders on any DEX.
diff --git a/client/core/types.go b/client/core/types.go
index f05a9f806e..0a9f3acb9f 100644
--- a/client/core/types.go
+++ b/client/core/types.go
@@ -86,14 +86,6 @@ type WalletForm struct {
AssetID uint32
Config map[string]string
Type string
- // ParentForm is the configuration settings for a parent asset. If this is a
- // token whose parent asset needs configuration, a non-nil ParentForm can be
- // supplied. This will cause CreateWallet to run in a special mode which
- // will create the parent asset's wallet synchronously, but schedule the
- // creation of the token wallet to occur asynchronously after the parent
- // wallet is fully synced, sending NoteTypeCreateWallet notifications to
- // update with progress.
- ParentForm *WalletForm
}
// WalletBalance is an exchange wallet's balance which includes various locked
@@ -124,12 +116,12 @@ type WalletState struct {
AssetID uint32 `json:"assetID"`
Version uint32 `json:"version"`
WalletType string `json:"type"`
+ Class asset.BlockchainClass `json:"class"`
Traits asset.WalletTrait `json:"traits"`
Open bool `json:"open"`
Running bool `json:"running"`
Balance *WalletBalance `json:"balance"`
Address string `json:"address"`
- Units string `json:"units"`
Encrypted bool `json:"encrypted"`
PeerCount uint32 `json:"peerCount"`
Synced bool `json:"synced"`
@@ -203,9 +195,6 @@ type SupportedAsset struct {
// Token is only populated for token assets.
Token *asset.Token `json:"token"`
UnitInfo dex.UnitInfo `json:"unitInfo"`
- // WalletCreationPending will be true if this wallet's parent wallet is
- // being synced before this wallet is created.
- WalletCreationPending bool `json:"walletCreationPending"`
}
// BondOptionsForm is used from the settings page to change the auto-bond
diff --git a/client/core/wallet.go b/client/core/wallet.go
index dd27454d41..eb9a8c3d7d 100644
--- a/client/core/wallet.go
+++ b/client/core/wallet.go
@@ -295,13 +295,13 @@ func (w *xcWallet) state() *WalletState {
Running: w.connector.On(),
Balance: w.balance,
Address: w.address,
- Units: winfo.UnitInfo.AtomicUnit,
Encrypted: len(w.encPass) > 0,
PeerCount: peerCount,
Synced: w.syncStatus.Synced,
SyncProgress: w.syncStatus.BlockProgress(),
SyncStatus: w.syncStatus,
WalletType: w.walletType,
+ Class: winfo.BlockchainClass,
Traits: w.traits,
Disabled: w.disabled,
Approved: tokenApprovals,
diff --git a/client/webserver/api.go b/client/webserver/api.go
index 9dde92640e..1a8fb4a92d 100644
--- a/client/webserver/api.go
+++ b/client/webserver/api.go
@@ -461,20 +461,11 @@ func (s *WebServer) apiNewWallet(w http.ResponseWriter, r *http.Request) {
return
}
defer zero(pass)
- var parentForm *core.WalletForm
- if f := form.ParentForm; f != nil {
- parentForm = &core.WalletForm{
- AssetID: f.AssetID,
- Config: f.Config,
- Type: f.WalletType,
- }
- }
// Wallet does not exist yet. Try to create it.
err = s.core.CreateWallet(pass, form.Pass, &core.WalletForm{
- AssetID: form.AssetID,
- Type: form.WalletType,
- Config: form.Config,
- ParentForm: parentForm,
+ AssetID: form.AssetID,
+ Type: form.WalletType,
+ Config: form.Config,
})
if err != nil {
s.writeAPIError(w, fmt.Errorf("error creating %s wallet: %w", unbip(form.AssetID), err))
diff --git a/client/webserver/jsintl.go b/client/webserver/jsintl.go
index 1ac06d56f3..bc28f8c390 100644
--- a/client/webserver/jsintl.go
+++ b/client/webserver/jsintl.go
@@ -53,7 +53,6 @@ const (
changeWalletTypeID = "CHANGE_WALLET_TYPE"
keepWalletTypeID = "KEEP_WALLET_TYPE"
walletReadyID = "WALLET_READY"
- walletPendingID = "WALLET_PENDING"
setupNeededID = "SETUP_NEEDED"
sendSuccessID = "SEND_SUCCESS"
reconfigSuccessID = "RECONFIG_SUCCESS"
@@ -274,7 +273,6 @@ var enUS = map[string]*intl.Translation{
changeWalletTypeID: {T: "change the wallet type"},
keepWalletTypeID: {T: "don't change the wallet type"},
setupNeededID: {T: "Setup Needed"},
- walletPendingID: {T: "Creating Wallet"},
sendSuccessID: {T: "{{ assetName }} Sent!"},
reconfigSuccessID: {T: "Wallet Reconfigured!"},
rescanStartedID: {T: "Wallet Rescan Running"},
@@ -548,7 +546,6 @@ var zhCN = map[string]*intl.Translation{
changeWalletTypeID: {T: "切换钱包类型"},
keepWalletTypeID: {T: "不要切换钱包类型"},
setupNeededID: {T: "需要设置"},
- walletPendingID: {T: "创建钱包"},
sendSuccessID: {T: "{{ assetName }} 发送!"},
reconfigSuccessID: {T: "钱包重置!"},
rescanStartedID: {T: "钱包扫描运行中"},
@@ -772,7 +769,6 @@ var plPL = map[string]*intl.Translation{
txTypeAccelerationID: {T: "Przyspieszenie"},
orderBttnQtyErrID: {T: "Ilość zamówień musi zostać określona."},
botTypeSimpleArbID: {T: "Prosty arbitraż"},
- walletPendingID: {T: "Tworzenie portfela"},
bondReservesID: {T: "Rezerwy kaucji"},
invalidValueID: {T: "nieprawidłowa wartość"},
disabledMsgID: {T: "portfel jest wyłączony"},
@@ -946,7 +942,6 @@ var deDE = map[string]*intl.Translation{
changeWalletTypeID: {T: "den Wallet-Typ ändern"},
keepWalletTypeID: {T: "den Wallet-Typ nicht ändern"},
setupNeededID: {T: "Einrichtung erforderlich"},
- walletPendingID: {T: "Erstelle Wallet"},
sendSuccessID: {T: "{{ assetName }} gesendet!"},
reconfigSuccessID: {T: "Wallet neu konfiguriert!"},
rescanStartedID: {T: "Wallet Rescan läuft"},
@@ -1159,7 +1154,6 @@ var ar = map[string]*intl.Translation{
keepWalletTypeID: {T: "لا تغير نوع المحفظة"},
walletReadyID: {T: "المحفظة جاهزة"},
setupNeededID: {T: "الإعداد مطلوب"},
- walletPendingID: {T: "إنشاء المحفظة"},
sendSuccessID: {T: "{{ assetName }} تم الإرسال!"},
reconfigSuccessID: {T: "تمت إعادة تهيئة المحفظة!!"},
rescanStartedID: {T: "إعادة فحص المحفظة قيد التشغيل"},
diff --git a/client/webserver/live_test.go b/client/webserver/live_test.go
index 86aff69cad..01c8d4f8cc 100644
--- a/client/webserver/live_test.go
+++ b/client/webserver/live_test.go
@@ -57,10 +57,9 @@ const (
var (
tCtx context.Context
- maxDelay = time.Second * 4
- epochDuration = time.Second * 30 // milliseconds
- feedPeriod = time.Second * 10
- creationPendingAsset uint32 = 0xFFFFFFFF
+ maxDelay = time.Second * 4
+ epochDuration = time.Second * 30 // milliseconds
+ feedPeriod = time.Second * 10
forceDisconnectWallet bool
wipeWalletBalance bool
gapWidthFactor = 1.0 // Should be 0 < gapWidthFactor <= 1.0
@@ -226,14 +225,13 @@ func mkSupportedAsset(symbol string, state *core.WalletState) *core.SupportedAss
}
return &core.SupportedAsset{
- ID: assetID,
- Symbol: symbol,
- Info: winfo,
- Wallet: state,
- Token: tinfos[assetID],
- Name: name,
- UnitInfo: unitInfo,
- WalletCreationPending: assetID == atomic.LoadUint32(&creationPendingAsset),
+ ID: assetID,
+ Symbol: symbol,
+ Info: winfo,
+ Wallet: state,
+ Token: tinfos[assetID],
+ Name: name,
+ UnitInfo: unitInfo,
}
}
@@ -1389,35 +1387,7 @@ func (c *TCore) CreateWallet(appPW, walletPW []byte, form *core.WalletForm) erro
c.mtx.Lock()
defer c.mtx.Unlock()
- // If this is a token, simulate parent syncing.
- token := asset.TokenInfo(form.AssetID)
- if token == nil || form.ParentForm == nil {
- c.createWallet(form, false)
- return nil
- }
-
- atomic.StoreUint32(&creationPendingAsset, form.AssetID)
-
- synced := c.createWallet(form.ParentForm, false)
-
- c.noteFeed <- &core.WalletCreationNote{
- Notification: db.NewNotification(core.NoteTypeCreateWallet, core.TopicCreationQueued, "", "", db.Data),
- AssetID: form.AssetID,
- }
-
- go func() {
- <-synced
- defer atomic.StoreUint32(&creationPendingAsset, 0xFFFFFFFF)
- if doubleCreateAsyncErr {
- c.noteFeed <- &core.WalletCreationNote{
- Notification: db.NewNotification(core.NoteTypeCreateWallet, core.TopicQueuedCreationFailed,
- "Test Error", "This failed because doubleCreateAsyncErr is true in live_test.go", db.Data),
- AssetID: form.AssetID,
- }
- return
- }
- c.createWallet(form, true)
- }()
+ c.createWallet(form, false)
return nil
}
diff --git a/client/webserver/locales/de-de.go b/client/webserver/locales/de-de.go
index a7b122c1c2..0da68403ae 100644
--- a/client/webserver/locales/de-de.go
+++ b/client/webserver/locales/de-de.go
@@ -612,8 +612,6 @@ var DeDE = map[string]*intl.Translation{
"err_with_cex_creds": {T: "Bei der Verbindung mit diesen Anmeldedaten ist ein Fehler aufgetreten"},
"approve_token_wallet_addr": {T: ` Adresse:`},
"Available fee balance": {T: "Verfügbares Gebührenguthaben"},
- "add_provider_tooltip": {T: "Du verwendest öffentliche RPC-Anbieter. Diese Anbieter könnten veraltet oder unzuverlässig werden. Wenn möglich, konfiguriere stattdessen deine eigenen vertrauenswürdigen Anbieter an."},
- "add_custom_rpc_provider": {T: "RPC Anbieter hinzufügen"},
"Profit": {T: "Profit"},
"Inventory": {T: "Inventar"},
"Booked orders": {T: "Gebuchte Aufträge"},
diff --git a/client/webserver/locales/en-us.go b/client/webserver/locales/en-us.go
index 0db0ae7c77..afcf08ae93 100644
--- a/client/webserver/locales/en-us.go
+++ b/client/webserver/locales/en-us.go
@@ -617,8 +617,6 @@ var EnUS = map[string]*intl.Translation{
"err_with_cex_creds": {T: "There was an error encountered connecting with these credentials"},
"approve_token_wallet_addr": {T: ` address:`},
"Available fee balance": {T: "Available fee balance"},
- "add_provider_tooltip": {T: "You are using the default set of public RPC providers. These providers could become outdated or unreliable. Specify your own trusted provider instead."},
- "add_custom_rpc_provider": {T: "Add a custom RPC provider"},
"Profit": {T: "Profit"},
"Inventory": {T: "Inventory"},
"Booked orders": {T: "Booked orders"},
diff --git a/client/webserver/locales/zh-cn.go b/client/webserver/locales/zh-cn.go
index 5c6a6c1fb6..a0b0b68de6 100644
--- a/client/webserver/locales/zh-cn.go
+++ b/client/webserver/locales/zh-cn.go
@@ -612,8 +612,6 @@ var ZhCN = map[string]*intl.Translation{
"err_with_cex_creds": {T: "连接这些凭证时遇到错误"},
"approve_token_wallet_addr": {T: ` address: 地址:`},
"Available fee balance": {T: "可用费用余额"},
- "add_provider_tooltip": {T: "您正在使用默认的公共 RPC 提供商。这些提供商可能会过时或不可靠。请指定您信任的提供商。"},
- "add_custom_rpc_provider": {T: "添加自定义RPC提供商"},
"Profit": {T: "利润"},
"Inventory": {T: "库存"},
"Booked orders": {T: "已预定订单"},
diff --git a/client/webserver/middleware.go b/client/webserver/middleware.go
index f028dfa43c..d5c92ab2ea 100644
--- a/client/webserver/middleware.go
+++ b/client/webserver/middleware.go
@@ -30,7 +30,7 @@ func (s *WebServer) securityMiddleware(next http.Handler) http.Handler {
w.Header().Set("X-Content-Type-Options", "nosniff")
w.Header().Set("Referrer-Policy", "no-referrer")
w.Header().Set("Content-Security-Policy", s.csp)
- w.Header().Set("Feature-Policy", "geolocation 'none'; midi 'none'; notifications 'none'; push 'none'; sync-xhr 'self'; microphone 'none'; camera 'none'; magnetometer 'none'; gyroscope 'none'; speaker 'none'; vibrate 'none'; fullscreen 'self'; payment 'none'")
+ w.Header().Set("Permissions-Policy", "geolocation=(), midi=(), sync-xhr=(self), microphone=(), camera=(), magnetometer=(), gyroscope=(), fullscreen=(self), payment=()")
next.ServeHTTP(w, r)
})
}
diff --git a/client/webserver/site/src/css/components.scss b/client/webserver/site/src/css/components.scss
index af2a3b3d3e..4135709b44 100644
--- a/client/webserver/site/src/css/components.scss
+++ b/client/webserver/site/src/css/components.scss
@@ -29,11 +29,21 @@ button {
font-size: .9rem;
}
+ &.micro {
+ padding: 0.1rem 0.25rem;
+ font-size: .8rem;
+ }
+
&.large {
padding: 0.5rem 1rem;
font-size: 1.25rem;
}
+ &.noborder {
+ border: none;
+ border-radius: 0;
+ }
+
&.feature {
background-color: var(--btn-feature-bg);
border-color: var(--btn-feature-border-color);
@@ -150,6 +160,14 @@ table {
background-color: #7772;
}
}
+
+ &.no-bottom-border > tbody {
+ border-bottom-style: none;
+ }
+
+ & > thead.unbold th {
+ font-weight: normal;
+ }
}
a {
@@ -160,12 +178,6 @@ a {
}
}
-@include media-breakpoint-up(md) {
- table#walletInfoTable {
- width: auto;
- }
-}
-
table.reg-asset-markets {
@extend .stylish-overflow;
diff --git a/client/webserver/site/src/css/main.scss b/client/webserver/site/src/css/main.scss
index 60db707897..3ab1f8e529 100644
--- a/client/webserver/site/src/css/main.scss
+++ b/client/webserver/site/src/css/main.scss
@@ -216,7 +216,9 @@ z-index: 1000;
}
[data-unit-box] {
- cursor: default;
+ @extend .hoverbg;
+
+ cursor: pointer;
position: relative;
overflow: visible;
diff --git a/client/webserver/site/src/css/wallets.scss b/client/webserver/site/src/css/wallets.scss
index 3d14469964..a51e9c1062 100644
--- a/client/webserver/site/src/css/wallets.scss
+++ b/client/webserver/site/src/css/wallets.scss
@@ -1,4 +1,4 @@
-.walletspage {
+[data-handler=wallets] {
.ico-unlocked {
color: var(--indicator-good);
}
@@ -110,18 +110,6 @@
#walletInfo {
border-left: none;
-
- table#walletInfoTable {
- td {
- padding: 2px 5px 2px 0;
- line-height: 1;
-
- &:last-child {
- text-align: right;
- padding-left: 1rem;
- }
- }
- }
}
#earlierTxs, #txViewBlockExplorer {
@@ -160,6 +148,18 @@
width: 18px;
height: 18px;
}
+
+ &:not(.multinet) .multinetonly {
+ display: none !important;
+ }
+
+ &.multinet .uninetonly {
+ display: none !important;
+ }
+
+ &:not(.token) .tokenonly {
+ display: none !important;
+ }
}
.peers-table-icon {
@@ -187,6 +187,13 @@
.negative-tx {
color: $danger;
}
+
+ #docs {
+ .plainlink:visited,
+ .plainlink:hover {
+ text-decoration: none;
+ }
+ }
}
@include media-breakpoint-up(xl) {
@@ -209,7 +216,7 @@
@include stylish-overflow;
}
- .walletspage {
+ [data-handler=wallets] {
#walletDetailsBox {
border-bottom: none !important;
}
@@ -225,7 +232,7 @@
}
@include media-breakpoint-up(sm) {
- .walletspage {
+ [data-handler=wallets] {
#walletDetailsBox {
#assetLogo {
width: 40px;
@@ -278,7 +285,7 @@
}
@include media-breakpoint-up(md) {
- .walletspage {
+ [data-handler=wallets] {
#sendReceive {
border-bottom: none;
}
diff --git a/client/webserver/site/src/html/docs.tmpl b/client/webserver/site/src/html/docs.tmpl
new file mode 100644
index 0000000000..c0f200a298
--- /dev/null
+++ b/client/webserver/site/src/html/docs.tmpl
@@ -0,0 +1,212 @@
+{{define "dcrDocs"}}
+
+
About Decred
+
+ Decred is a blockchain-based cryptocurrency with a strong focus on community input, open governance, and sustainable funding for development.
+ Decred uses a hybrid proof-of-work (PoW) / proof-of-stake (PoS) mining model that gives ultra-high security with a fraction of the resource requirements.
+ Decred's on-chain governance system is tied in to the PoS system, and includes the world's first fully-decentralized development treasury fully-controlled by the stakeholders.
+ Combined with an on-chain voting mechanism, this means that stakeholders have complete control over all blockchain upgrades and development funding.
+ This is an alignment of incentives that no other blockchain can boast.
+
+ Bitcoin is the first and most widely recognized cryptocurrency, created in 2009 by the pseudonymous developer Satoshi Nakamoto.
+ It operates on a decentralized, peer-to-peer network using blockchain technology, where transactions are recorded transparently and securely without the need for intermediaries.
+ Bitcoin relies on a proof-of-work (PoW) consensus mechanism, where miners validate transactions and secure the network by solving complex cryptographic puzzles.
+ With a fixed supply of 21 million coins, Bitcoin is often considered digital gold, serving as a store of value, hedge against inflation, and alternative to traditional fiat currencies.
+ Its decentralized nature and limited supply make it a key asset in the cryptocurrency and financial ecosystems.
+
+ Ethereum is a decentralized, open-source blockchain platform that enables smart contracts and decentralized applications (dApps) to run without intermediaries.
+ Launched in 2015 by Vitalik Buterin and others, it introduced the Ethereum Virtual Machine (EVM), allowing developers to execute code on a global, distributed network.
+ Its native cryptocurrency, Ether (ETH), is used for transactions, staking, and gas fees that power computations.
+ Ethereum transitioned from a proof-of-work (PoW) to a proof-of-stake (PoS) consensus mechanism with the Merge in 2022, improving energy efficiency and security.
+ As a foundational layer for DeFi, NFTs, and other Web3 applications, Ethereum continues to evolve with scalability upgrades like rollups and sharding.
+
+ USDC (USD Coin) is a fully collateralized, fiat-backed stablecoin pegged 1:1 to the U.S. dollar, issued by Circle and governed by the Centre consortium.
+ It operates on multiple blockchains, including Ethereum, Polygon, and Base, and is widely used for payments, remittances, and decentralized finance (DeFi).
+ Each USDC token is backed by dollar-denominated reserves held in regulated financial institutions, with regular audits to ensure transparency.
+ Unlike algorithmic stablecoins, USDC maintains its peg through direct redemption mechanisms, allowing users to exchange it for USD at any time.
+ It is one of the most trusted stablecoins due to its regulatory compliance and transparency.
+
+ USDT (Tether) is a widely used stablecoin pegged 1:1 to the U.S. dollar, issued by Tether Limited.
+ It operates on multiple blockchains, including Ethereum, Polygon, and Base, facilitating fast and low-cost transactions in crypto markets.
+ USDT is commonly used for trading, remittances, and decentralized finance (DeFi), offering liquidity and stability in the volatile crypto ecosystem.
+ Unlike fully-regulated stablecoins, Tether has faced scrutiny over its reserve transparency, though it claims to be backed by cash, cash equivalents, and other assets.
+ Despite regulatory concerns, USDT remains the most traded stablecoin and a dominant force in the digital asset space.
+
+ Litecoin is a decentralized cryptocurrency created in 2011 by former Google engineer Charlie Lee as a faster and more lightweight alternative to Bitcoin.
+ It uses a proof-of-work (PoW) consensus mechanism with the Scrypt hashing algorithm, allowing for quicker block times (2.5 minutes vs. Bitcoin's 10 minutes) and lower transaction fees.
+ While often considered the "silver to Bitcoin's gold," Litecoin maintains strong network security and decentralization while offering improved scalability.
+ It has been a testing ground for innovations like Segregated Witness (SegWit) and the Lightning Network, which were later adopted by Bitcoin.
+ Despite facing competition from newer blockchains, Litecoin remains one of the most widely used and trusted cryptocurrencies.
+
+ Dogecoin is a decentralized, open-source cryptocurrency created in 2013 by Billy Markus and Jackson Palmer as a lighthearted alternative to Bitcoin, featuring the Shiba Inu meme as its mascot.
+ Initially a joke, Dogecoin gained a strong community and became popular for tipping, charitable donations, and microtransactions due to its low fees and fast transaction speeds.
+ It operates on a proof-of-work (PoW) system similar to Litecoin, with an unlimited supply to encourage spending rather than hoarding.
+ Over time, Dogecoin has seen mainstream attention, including endorsements from figures like Elon Musk, and remains a widely recognized and actively traded cryptocurrency.
+
+ Dash is a decentralized cryptocurrency launched in 2014 as a fork of Bitcoin, designed for fast, low-cost digital payments.
+ Dash features innovations like InstantSend, which enables near-instant transactions, and ChainLocks, which enhances security against 51% attacks.
+ It also has a unique two-tier network with masternodes that facilitate governance, private transactions (via PrivateSend), and network upgrades.
+ With a self-funding treasury system, Dash continues to evolve as a payment-focused cryptocurrency used for remittances and merchant adoption worldwide.
+
+ DigiByte is a decentralized, open-source cryptocurrency launched in 2014 by Jared Tate, designed to improve speed, security, and scalability compared to Bitcoin.
+ It features a multi-algorithm proof-of-work (PoW) consensus system, utilizing five different mining algorithms to enhance security and decentralization.
+ DigiByte also introduced DigiShield, an advanced difficulty adjustment mechanism that helps protect against mining fluctuations.
+ With 15-second block times, it offers significantly faster transactions than Bitcoin and many other cryptocurrencies.
+ Despite being a grassroots project without corporate backing, DigiByte has built a strong community and remains focused on decentralized digital payments and blockchain security applications.
+
+ Zcash is a privacy-focused cryptocurrency launched in 2016, based on Bitcoin's code but with enhanced anonymity features.
+ It utilizes zero-knowledge proofs (zk-SNARKs) to enable fully private transactions while still being verifiable on the blockchain.
+ Users can choose between transparent and shielded addresses, allowing flexibility in privacy.
+ Zcash maintains a fixed supply of 21 million coins, similar to Bitcoin, and uses a proof-of-work (PoW) consensus mechanism for network security.
+ While it provides financial privacy, Zcash also supports compliance features for regulated institutions, making it one of the most advanced privacy coins in the cryptocurrency space.
+
+ Firo, formerly known as Zcoin, is a privacy-focused cryptocurrency launched in 2016 that enhances transactional anonymity using advanced cryptographic techniques.
+ It originally implemented the Zerocoin protocol for privacy but later transitioned to Lelantus and Lelantus Spark, which provide trustless, on-chain privacy without the need for a trusted setup.
+ Firo uses a hybrid proof-of-work (PoW) and masternode system, which helps secure the network while enabling features like instant and private transactions.
+ With a strong emphasis on financial privacy and decentralization, Firo continues to be a leading privacy coin, balancing anonymity, usability, and security in the cryptocurrency ecosystem.
+
+ Bitcoin Cash is a decentralized cryptocurrency that emerged in 2017 as a hard fork of Bitcoin, aiming to improve scalability and transaction efficiency.
+ It increases the block size limit to 32MB, allowing for faster and cheaper transactions compared to Bitcoin's 1MB blocks.
+ Bitcoin Cash retains Bitcoin's proof-of-work (PoW) consensus mechanism but focuses on being a peer-to-peer electronic cash system, prioritizing usability for everyday payments.
+ Despite ideological and technical splits within its community—leading to further forks like Bitcoin SV (BSV)—BCH remains one of the most widely used cryptocurrencies for low-cost, fast transactions and merchant adoption.
+
+ Polygon is a layer-2 scaling solution for Ethereum that enhances transaction speed and reduces costs while maintaining security and decentralization.
+ Originally launched as Matic Network in 2017, it rebranded to Polygon in 2021, expanding its vision to become a multi-chain ecosystem supporting various scaling technologies,
+ including sidechains, rollups, and zero-knowledge (ZK) proofs.
+ Polygon enables developers to build and deploy scalable decentralized applications (dApps) with lower gas fees while leveraging Ethereum's robust security.
+ Its native token, POL, is used for governance, staking, and transaction fees.
+ With growing adoption in DeFi, gaming, and enterprise blockchain solutions, Polygon plays a key role in Ethereum's scalability and mass adoption.
+