Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 26 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,34 @@ Tokencore is a Java multi-chain wallet core library for exchange backends, custo
- Offline transaction signing for major chain families

Supported chains include:
- **EVM**: Ethereum
- **Bitcoin family**: Bitcoin, Litecoin, Dogecoin, Dash, Bitcoin Cash, Bitcoin SV
- **EVM**: Ethereum (built-in), plus **any EVM chain** you register at runtime (`chainId`, default BIP44 path)
- **Bitcoin family**: Bitcoin, Litecoin, Dogecoin, Dash, Bitcoin Cash, Bitcoin SV, plus **bitcoin-style UTXO chains** registered with address/BIP32 headers
- **Others**: TRON, Filecoin, EOS

### Global chain support (registry + catalog)

Tokencore does not ship an authoritative list of every network in the world. Instead it provides:

- **`ChainRegistry`**: register EVM chains (`EvmChainRegistration`) and bitcoin-style UTXO chains (`UtxoChainRegistration` + `CustomBitcoinStyleNetParams`).
- **`ChainCatalogLoader`**: bulk-register from a JSON array (e.g. app-shipped file or data filtered from [chainlist](https://chainlist.org)).
- **Wallet identity**: multiple EVM chains share the same address for the same key; wallets are keyed by **`(chainType, address)`** — use a **unique `chainType` string per chain** (e.g. `POLYGON`, `ARBITRUM_ONE`).

```java
import org.consenlabs.tokencore.wallet.chain.*;
import org.consenlabs.tokencore.wallet.model.BIP44Util;

// Single EVM L2
ChainRegistry.getInstance().registerEvm(
new EvmChainRegistration("MYL2", 84532L, BIP44Util.defaultEvmAccountZeroPath(60)));

// Bulk from JSON: [ { "chainType": "...", "family": "EVM", "chainId": 1, "slip44": 60 } ]
ChainCatalogLoader.registerAllFromJson("[...]");
```

**EIP-1559 (type-2)**: use `org.consenlabs.tokencore.wallet.transaction.Eip1559Transaction` (raw tx = `0x02 || rlp(...)`).

**EIP-712**: `Eip712Hasher.hashTypedDataV4(JsonNode)` and `TypedDataSigner.signTypedDataV4(JsonNode, privateKeyBytes)`.

---

## Core Features (Recommended Minimum)
Expand Down
84 changes: 61 additions & 23 deletions src/main/java/org/consenlabs/tokencore/wallet/Identity.java
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,10 @@
import org.consenlabs.tokencore.foundation.utils.ByteUtil;
import org.consenlabs.tokencore.foundation.utils.MnemonicUtil;
import org.consenlabs.tokencore.foundation.utils.NumericUtil;
import org.consenlabs.tokencore.wallet.chain.ChainFamily;
import org.consenlabs.tokencore.wallet.chain.ChainRegistry;
import org.consenlabs.tokencore.wallet.chain.EvmChainRegistration;
import org.consenlabs.tokencore.wallet.chain.UtxoChainRegistration;
import org.consenlabs.tokencore.wallet.keystore.*;
import org.consenlabs.tokencore.wallet.model.*;
import org.consenlabs.tokencore.wallet.transaction.EthereumSign;
Expand Down Expand Up @@ -165,35 +169,21 @@ public List<Wallet> deriveWalletsByMnemonics(List<String> chainTypes, String pas
List<Wallet> wallets = new ArrayList<>();
for (String chainType : chainTypes) {
Wallet wallet;
switch (chainType) {
case ChainType.BITCOIN:
wallet = deriveBitcoinWallet(mnemonics, password, this.getMetadata().getSegWit());
ChainFamily family = ChainRegistry.getInstance().resolveFamily(chainType);
switch (family) {
case BITCOIN_STYLE_UTXO:
wallet = deriveUtxoWalletByChainType(chainType, mnemonics, password, this.getMetadata().getSegWit());
break;
case ChainType.ETHEREUM:
wallet = deriveEthereumWallet(mnemonics, password);
case EVM:
wallet = deriveEvmWalletByChainType(chainType, mnemonics, password);
break;
case ChainType.LITECOIN:
wallet = deriveLitecoinWallet(mnemonics, password, this.getMetadata().getSegWit());
break;
case ChainType.DOGECOIN:
wallet = deriveDogecoinWallet(mnemonics, password, this.getMetadata().getSegWit());
break;
case ChainType.DASH:
wallet = deriveDashWallet(mnemonics, password, this.getMetadata().getSegWit());
break;
case ChainType.BITCOINSV:
wallet = deriveBitcoinSVWallet(mnemonics, password, this.getMetadata().getSegWit());
break;
case ChainType.BITCOINCASH:
wallet = deriveBitcoinCASHWallet(mnemonics, password, this.getMetadata().getSegWit());
break;
case ChainType.EOS:
case EOS:
wallet = deriveEOSWallet(mnemonics, password);
break;
case ChainType.TRON:
case TRON:
wallet = deriveTronWallet(mnemonics, password);
break;
case ChainType.FILECOIN:
case FILECOIN:
wallet = deriveFilecoinWallet(mnemonics, password);
break;
default:
Expand All @@ -206,6 +196,54 @@ public List<Wallet> deriveWalletsByMnemonics(List<String> chainTypes, String pas
return wallets;
}

private Wallet deriveEvmWalletByChainType(String chainType, List<String> mnemonics, String password) {
EvmChainRegistration reg = ChainRegistry.getInstance().getEvmRegistration(chainType);
if (reg == null) {
throw new TokenException(String.format("Doesn't support deriving %s wallet", chainType));
}
Metadata walletMetadata = new Metadata();
walletMetadata.setChainType(reg.getChainType());
walletMetadata.setPasswordHint(this.getMetadata().getPasswordHint());
walletMetadata.setSource(this.getMetadata().getSource());
walletMetadata.setName(reg.getChainType());
IMTKeystore keystore = V3MnemonicKeystore.create(walletMetadata, password, mnemonics, reg.getDefaultMnemonicPath());
return WalletManager.createWallet(keystore);
}

private Wallet deriveUtxoWalletByChainType(String chainType, List<String> mnemonics, String password, String segWit) {
if (ChainType.BITCOIN.equalsIgnoreCase(chainType)) {
return deriveBitcoinWallet(mnemonics, password, segWit);
}
if (ChainType.LITECOIN.equalsIgnoreCase(chainType)) {
return deriveLitecoinWallet(mnemonics, password, segWit);
}
if (ChainType.DOGECOIN.equalsIgnoreCase(chainType)) {
return deriveDogecoinWallet(mnemonics, password, segWit);
}
if (ChainType.DASH.equalsIgnoreCase(chainType)) {
return deriveDashWallet(mnemonics, password, segWit);
}
if (ChainType.BITCOINSV.equalsIgnoreCase(chainType)) {
return deriveBitcoinSVWallet(mnemonics, password, segWit);
}
if (ChainType.BITCOINCASH.equalsIgnoreCase(chainType)) {
return deriveBitcoinCASHWallet(mnemonics, password, segWit);
}
UtxoChainRegistration reg = ChainRegistry.getInstance().getUtxoRegistration(chainType);
if (reg == null) {
throw new TokenException(String.format("Doesn't support deriving %s wallet", chainType));
}
Metadata walletMetadata = new Metadata();
walletMetadata.setChainType(reg.getChainType());
walletMetadata.setPasswordHint(this.getMetadata().getPasswordHint());
walletMetadata.setSource(this.getMetadata().getSource());
walletMetadata.setNetwork(this.getMetadata().getNetwork());
walletMetadata.setName(reg.getChainType());
walletMetadata.setSegWit(segWit);
IMTKeystore keystore = HDMnemonicKeystore.create(walletMetadata, password, mnemonics, reg.getDefaultMnemonicPath());
return WalletManager.createWallet(keystore);
}


private static Identity tryLoadFromFile() {
try {
Expand Down
31 changes: 12 additions & 19 deletions src/main/java/org/consenlabs/tokencore/wallet/WalletManager.java
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@
import org.consenlabs.tokencore.foundation.utils.NumericUtil;
import org.consenlabs.tokencore.wallet.address.AddressCreatorManager;
import org.consenlabs.tokencore.wallet.address.EthereumAddressCreator;
import org.consenlabs.tokencore.wallet.chain.ChainFamily;
import org.consenlabs.tokencore.wallet.chain.ChainRegistry;
import org.consenlabs.tokencore.wallet.keystore.*;
import org.consenlabs.tokencore.wallet.model.*;
import org.consenlabs.tokencore.wallet.validators.PrivateKeyValidator;
Expand Down Expand Up @@ -165,21 +167,17 @@ public static Wallet importWalletFromMnemonic(Metadata metadata, @Nullable Strin
IMTKeystore keystore = null;
List<String> mnemonicCodes = MnemonicUtil.toMnemonicCodes(mnemonic);
MnemonicUtil.validateMnemonics(mnemonicCodes);
switch (metadata.getChainType()) {
case ChainType.ETHEREUM:
case ChainType.TRON:
case ChainType.FILECOIN:
ChainFamily family = ChainRegistry.getInstance().resolveFamily(metadata.getChainType());
switch (family) {
case EVM:
case TRON:
case FILECOIN:
keystore = V3MnemonicKeystore.create(metadata, password, mnemonicCodes, path);
break;
case ChainType.BITCOIN:
case ChainType.LITECOIN:
case ChainType.DASH:
case ChainType.DOGECOIN:
case ChainType.BITCOINCASH:
case ChainType.BITCOINSV:
case BITCOIN_STYLE_UTXO:
keystore = HDMnemonicKeystore.create(metadata, password, mnemonicCodes, path);
break;
case ChainType.EOS:
case EOS:
keystore = EOSKeystore.create(metadata, password, accountName, mnemonicCodes, path, permissions);
break;
default:
Expand All @@ -194,7 +192,7 @@ public static Wallet importWalletFromMnemonic(Metadata metadata, String mnemonic
}

public static Wallet findWalletByPrivateKey(String chainType, String network, String privateKey, String segWit) {
if (ChainType.ETHEREUM.equals(chainType)) {
if (ChainRegistry.getInstance().isRegisteredEvm(chainType)) {
new PrivateKeyValidator(privateKey).validate();
}
Network net = new Network(network);
Expand Down Expand Up @@ -403,15 +401,10 @@ private static String deriveAddressByChain(String chainType, Metadata metadata,


private static boolean isBitcoinFamily(String chainType) {
return ChainType.BITCOIN.equalsIgnoreCase(chainType)
|| ChainType.LITECOIN.equalsIgnoreCase(chainType)
|| ChainType.DOGECOIN.equalsIgnoreCase(chainType)
|| ChainType.DASH.equalsIgnoreCase(chainType)
|| ChainType.BITCOINCASH.equalsIgnoreCase(chainType)
|| ChainType.BITCOINSV.equalsIgnoreCase(chainType);
return ChainRegistry.getInstance().isRegisteredUtxo(chainType);
}
private static boolean isEvmFamily(String chainType) {
return ChainType.ETHEREUM.equalsIgnoreCase(chainType) || chainType.toUpperCase().contains("EVM");
return ChainRegistry.getInstance().isRegisteredEvm(chainType);
}

private static IMTKeystore mustFindKeystoreById(String id) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@
import org.bitcoinj.core.NetworkParameters;
import org.bitcoinj.params.MainNetParams;
import org.bitcoinj.params.TestNet3Params;
import org.consenlabs.tokencore.wallet.chain.ChainRegistry;
import org.consenlabs.tokencore.wallet.chain.UtxoChainRegistration;
import org.consenlabs.tokencore.wallet.model.ChainType;
import org.consenlabs.tokencore.wallet.model.Messages;
import org.consenlabs.tokencore.wallet.model.Metadata;
Expand All @@ -12,7 +14,7 @@
public class AddressCreatorManager {

public static AddressCreator getInstance(String type, boolean isMainnet, String segWit) {
if (ChainType.ETHEREUM.equals(type)) {
if (ChainType.ETHEREUM.equals(type) || ChainRegistry.getInstance().isRegisteredEvm(type)) {
return new EthereumAddressCreator();
}else if (ChainType.FILECOIN.equals(type)) {
return new FilecoinAddressCreator();
Expand Down Expand Up @@ -40,6 +42,10 @@ public static AddressCreator getInstance(String type, boolean isMainnet, String
}
return new BitcoinAddressCreator(network);
} else {
UtxoChainRegistration reg = ChainRegistry.getInstance().getUtxoRegistration(type);
if (reg != null) {
return new BitcoinAddressCreator(reg.getNetworkParameters());
}
throw new TokenException(Messages.WALLET_INVALID_TYPE);
}
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
package org.consenlabs.tokencore.wallet.chain;

import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.fasterxml.jackson.annotation.JsonProperty;

/**
* JSON row for {@link ChainCatalogLoader}. Extra fields are ignored.
*/
@JsonIgnoreProperties(ignoreUnknown = true)
public class ChainCatalogEntry {

private String chainType;

/**
* EVM, UTXO (bitcoin-style), or built-in TRON, FILECOIN, EOS (no-op registration for catalog consistency).
*/
private String family;

private Long chainId;

private Integer slip44;

private String defaultMnemonicPath;

private Integer addressHeader;
private Integer p2shHeader;
private Integer dumpedPrivateKeyHeader;
private Integer bip32HeaderPub;
private Integer bip32HeaderPriv;
private Boolean useMainnetDifficultyGenesis;

public String getChainType() {
return chainType;
}

public void setChainType(String chainType) {
this.chainType = chainType;
}

public String getFamily() {
return family;
}

public void setFamily(String family) {
this.family = family;
}

public Long getChainId() {
return chainId;
}

public void setChainId(Long chainId) {
this.chainId = chainId;
}

public Integer getSlip44() {
return slip44;
}

public void setSlip44(Integer slip44) {
this.slip44 = slip44;
}

public String getDefaultMnemonicPath() {
return defaultMnemonicPath;
}

public void setDefaultMnemonicPath(String defaultMnemonicPath) {
this.defaultMnemonicPath = defaultMnemonicPath;
}

public Integer getAddressHeader() {
return addressHeader;
}

public void setAddressHeader(Integer addressHeader) {
this.addressHeader = addressHeader;
}

public Integer getP2shHeader() {
return p2shHeader;
}

public void setP2shHeader(Integer p2shHeader) {
this.p2shHeader = p2shHeader;
}

public Integer getDumpedPrivateKeyHeader() {
return dumpedPrivateKeyHeader;
}

public void setDumpedPrivateKeyHeader(Integer dumpedPrivateKeyHeader) {
this.dumpedPrivateKeyHeader = dumpedPrivateKeyHeader;
}

public Integer getBip32HeaderPub() {
return bip32HeaderPub;
}

public void setBip32HeaderPub(Integer bip32HeaderPub) {
this.bip32HeaderPub = bip32HeaderPub;
}

public Integer getBip32HeaderPriv() {
return bip32HeaderPriv;
}

public void setBip32HeaderPriv(Integer bip32HeaderPriv) {
this.bip32HeaderPriv = bip32HeaderPriv;
}

public Boolean getUseMainnetDifficultyGenesis() {
return useMainnetDifficultyGenesis;
}

public void setUseMainnetDifficultyGenesis(Boolean useMainnetDifficultyGenesis) {
this.useMainnetDifficultyGenesis = useMainnetDifficultyGenesis;
}
}
Loading
Loading