Contents

BCSuite/Forging

Forging — A convenient Web3 wallet toolkit for easily generating, importing, and exporting single/batch HD & non-HD wallets. Pluggable design for any chain, with Solana + full EVM support out of the box.

Forging

Forging is a convenient and developer-friendly Web3 wallet toolkit that supports generating, importing, and exporting both single and batch HD and non-HD wallets.

Built with a modular, pluggable architecture, it allows seamless extension to any chain and ships with first-class support for Solana and all EVM-compatible chains (Ethereum, BNB Chain, Polygon, Arbitrum, etc.).

[iOS 14+] [Swift 5.3+] [[Swift Package Manager]](https://img.shields.io/badge/Swift_Package_Manager-compatible-orange?style=flat-square) [[Platforms]](https://img.shields.io/badge/Platforms-macOS_iOS-Green?style=flat-square)

Feature

  • Generate single or batch HD/Non-HD wallets for any specific chain with one click.
  • Import single or batch HD/non-HD wallets for any specific chain with one click.
  • Export single or batch HD/Non-HD wallets for any specific chain with one click.
  • Generate single or batch HD/non-HD wallets across multiple chains with one click.
  • Import single or batch HD/non-HD wallets across multiple chains with one click.

Installation

SPM

.package(url: "https://https://github.com/BCSuite/Forging.git", from: "0.0.1")

Usage

### Non-HD

#### Generate

- **Single chain**.

  - Single wallet

    ```
     // Solana.
    let generator = FGNonHDWalletUniGenerator.solana()
    let wallet = try generator.generate()
    print("address:\(wallet.address), privKey:\(wallet.privKey)")
    
    // EVM.
    let generator = FGNonHDWalletUniGenerator.evm()
    let wallet = try generator.generate()
    print("address:\(wallet.address), privKey:\(wallet.privKey)"
    ```

  - batch wallets

    ```
    // Solana.
    let genCnt = 201
    let generator = FGNonHDWalletUniGenerator.solana()
    let wallets = try await generator.batchGenerate(inCount: genCnt)
    for wallet in wallets {
      print("address:\(wallet.address), privKey:\(wallet.privKey)")
    }
    
    // EVM.
    let genCnt = 101
    let generator = FGNonHDWalletUniGenerator.evm()
    let wallets = try await generator.batchGenerate(inCount: genCnt)
    for wallet in wallets {
        print("address:\(wallet.address), privKey:\(wallet.privKey)")
    }
    ```

    The `batchGenerate` will ignoring failed cases and continue generating by default. If want to stop once any generation fails, set the `isInterruptible`  param to  `true` .

    ```
    let wallets = try await generator.batchGenerate(inCount: genCnt, isInterruptible: true)
    ```

    

- **Multi-chain**.

  - Single wallet

    ```
    // Set up all needed EVM chains except Ethereum which will be contained by default.
    let evmSharedChains: [any FGChainDescribable.Type] = [FGBaseChain.self, FGOptimismChain.self]
    
    // Configure the generation handler of each chain.
    let handlers = [FGNonHDGenHandler.solana(), FGNonHDGenHandler.evm(serviceSharedChains: evmSharedChains)]
    
    // Create a multi-chain tasks processor.
    let generator = FGMultiChainWalletProcessor(chainHandlers: handlers)
    
    // Generate with the operation kind: FGMultiChainNonHDWalletGenKind.
    let walletMap = try await generator.process(with: .single)
    for (chainName, wallets) in walletMap {
        // Wallets only contains one wallet.
        print("\(chainName)->wallet\(String(describing: wallets.first))")
    }
    
    /// Output ///
    Optimism->wallet: Optional(Forging.FGCryptoWallet(address: "0x1..6”, privKey: “d….9”, mnemonic: nil))
    Solana->Optional(Forging.FGCryptoWallet(address: "GF..JoK", privKey: "59E..”, mnemonic: nil))
    Ethereum-> Optional(Forging.FGCryptoWallet(address: "0x1..b”, privKey: “d..9”, mnemonic: nil))
    Base -> Optional(Forging.FGCryptoWallet(address: "0x1..b”, privKey: “d..9”, mnemonic: nil)
    ```

  - batch wallets

    ```
    let genCnt = 20
    // Set up all needed EVM chains except Ethereum which will be contained by default.
    let evmSharedChains: [any FGChainDescribable.Type] = [FGBaseChain.self, FGOptimismChain.self]
    
    // Configure the generation handler of each chain.
    let handlers = [FGNonHDGenHandler.solana(), FGNonHDGenHandler.evm(serviceSharedChains: evmSharedChains)]
    
    // Create a multi-chain tasks processor.
    let generator = FGMultiChainWalletProcessor(chainHandlers: handlers)
    
    // Generate with the operation kind of FGMultiChainNonHDWalletGenKind.batch(count:).
    let walletMap = try await generator.process(with: .batch(count: genCnt))
    for (chainName, wallets) in walletMap {
        print("\(chainName):")
        for wallet in wallets {
            print("address:\(wallet.address), privKey:\(wallet.privKey)")
        }
    }
    
    /// Output ///
    Optimism:
    address:0x1…1, privKey:9…8
    address:0x2…5, privKey:7...c
    address:0xf..9, privKey:c..5
    Ethereum:
    address:0x1…1, privKey:9…8
    address:0x2..5, privKey:7..c
    address:0xf..9, privKey:c..5
    Base:
    address:0x1…1, privKey:9…8
    address:0x2..5, privKey:7..c
    address:0xf..9, privKey:c..5
    Solana:
    address:A…k, privKey:5…a
    address:D…s, privKey:3…s
    address:G…5, privKey:M…u
    ```




#### Import

- Solana

  ```
  let importer = FGSingleSolNonHDWalletImporter()
  let privKey = "y..a"
  let wallet = try importer.importWallet(from: privKey)
  print("address:\(wallet.address)")
  ```

- EVM

  ```
   let importer = FGSingleEVMNonHDWalletImporter()
   let privKey = "a..n"
   let importedWallet = try importer.importWallet(from: wallet.privKey)
   print("address:\(wallet.address)")
  ```

  

### HD 

#### Generate

- **Single chain**

  - Single wallet

    ```
    /// Solana ///
    
    // Generate a random Solana wallet with 12 words mnemonic.
    let generator = FGHDWalletUniGenerator.solana()
    let wallet = try generator.generate()
    print("address:\(wallet.address), privKey:\(wallet.privKey), mnemonic:\(wallet.mnemonic ?? "")")
    
    // Specify the mnemonic words count.
    // - 12 words(default): FGMnemonic12WordsSize
    // - 15 words: FGMnemonic15WordsSize
    // - 18 words: FGMnemonic18WordsSize
    // - 21 words: FGMnemonic21WordsSize
    // - 24 words: FGMnemonic24WordsSize
    let generator = FGHDWalletUniGenerator.solana()
    let mnemonicSize = FGMnemonic24WordsSize.self // or other size.
    let wallet = try generator.generate(atSize: mnemonicSize)
    print("address:\(wallet.address), privKey:\(wallet.privKey), mnemonic:\(wallet.mnemonic ?? "")")
    
    // Generate a Solana wallet from a given mnemonic.
    let generator = FGHDWalletUniGenerator.solana()
    let mnemonic = "xx xx..."
    let wallet = try generator.generate(from: mnemonic)
    print("address:\(wallet.address), privKey:\(wallet.privKey), mnemonic:\(wallet.mnemonic ?? "")")
      
      
    /// EVM ///
    
    // Gereate a random EVM wallet with 12 words mnemonic.
    let generator = FGHDWalletUniGenerator.evm()
    let wallet = try generator.generate()
    
    // Generate a EVM wallet from a given mnemonic.
    let generator = FGHDWalletUniGenerator.evm()
    let mnemonic = "xx xx..."
    let wallet = try generator.generate(from: mnemonic)
    ```

  - Batch wallets

    ```
    /// Solana ///
    let genCnt = 1000
    let generator = FGHDWalletUniGenerator.solana()
    
    // Randomly generate Solana wallets with 12 words mnemonic.
    let wallets = try await generator.batchGenerate(inCount: genCnt)
    
    // Specify the mnemonic words count(default is 12 words).
    let mnemonicSize = 
    let wallets = try await generator.batchGenerate(inCount: genCnt,
                                                    mnemonicSize: FGMnemonic24WordsSize.self)
                                                    
    // Generate Solana wallets given mnemonics.
    let mnemonics = ["xx", "yy"]
    let wallets = try await generator.batchGenerate(from: mnemonics)
    
    
    /// EVM ///
    
    let generator = FGHDWalletUniGenerator.evm()
    
    // Randomly generate EVM wallets with 12 words mnemonic.
    let wallets = try await generator.batchGenerate(inCount: genCnt)
    
    // Specify the mnemonic words count(default is 12 words).
    let mnemonicSize = 
    let wallets = try await generator.batchGenerate(inCount: genCnt,
                                                    mnemonicSize: FGMnemonic24WordsSize.self)
                                                    
    // Generate EVM wallets given mnemonics.
    let mnemonics = ["xx", "yy"]
    let wallets = try await generator.batchGenerate(from: mnemonics)
    ```

    

- **Multi-chain**

  - Single wallet

    ```
    // Set up all needed EVM chains except Ethereum which will be contained by default.
    let evmSharedChains: [any FGChainDescribable.Type] = [FGBaseChain.self, FGOptimismChain.self]
    
    // Configure all handlers to generate wallets for the corresponding chains.
    let handlers = [FGHDGenHandler.solana(), FGHDGenHandler.evm(serviceSharedChains: evmSharedChains)]
    
    // Create a multi-chain tasks processor.
    let generator = FGMultiChainWalletProcessor(chainHandlers: handlers)
    
    // Generate with the operation kind: FGMultiChainHDWalletGenKind.
    //  - singleRandom(mnemonicSize:): Generate a single wallet by generating random mnemomic for each chain.
    //  - singleSpecific(mnemomic:): Generate a single wallet by using a given mnemomic for each chain
    //  - batchRandom(count:, mnemonicSize:, isInterruptible:): Generate  the specific count of // wallets by using random mnemomics  for each chain.
    //  - batchSpecific(mnemomics:, isInterruptible:): Generate the specific count of  wallets by using the given mnemomics  for each chain.
    let walletMap = try await generator.process(with: .singleRandom())
    for (chainName, wallets) in walletMap {
       print("\(chainName):")
        for wallet in wallets {
            print("address:\(wallet.address), privKey:\(wallet.privKey), mnemonic:\(wallet.mnemonic)")
        }
    }
    
    /// Output ///
    Optimism:
    address:0x1…1, privKey:9…8
    Ethereum:
    address:0x1…1, privKey:9…8
    Base:
    address:0x1…1, privKey:9…8
    Solana:
    address:A…k, privKey:5…a
    ```

  - Batch wallets

    ```
    // Set up all needed EVM chains except Ethereum which will be contained by default.
    let evmSharedChains: [any FGChainDescribable.Type] = [FGBaseChain.self, FGOptimismChain.self]
    
    // Configure all handlers to generate wallets for the corresponding chains.
    let handlers = [FGNonHDGenHandler.solana(), FGNonHDGenHandler.evm(serviceSharedChains: evmSharedChains)]
    
    // Create a multi-chain tasks processor.
    let generator = FGMultiChainWalletProcessor(chainHandlers: handlers)
    let genCnt = 3
    // Use the operation kind of .batch(count:).
    let walletMap = try await generator.process(with: .batch(count: genCnt))
    for (chainName, wallets) in walletMap {
       print("\(chainName):")
        for wallet in wallets {
            print("address:\(wallet.address), privKey:\(wallet.privKey), mnemonic:\(wallet.mnemonic)")
        }
    }
    
    /// Output ///
    Optimism:
    address:0x1…1, privKey:9…8
    address:0x2…5, privKey:7...c
    address:0xf..9, privKey:c..5
    Ethereum:
    address:0x1…1, privKey:9…8
    address:0x2..5, privKey:7..c
    address:0xf..9, privKey:c..5
    Base:
    address:0x1…1, privKey:9…8
    address:0x2..5, privKey:7..c
    address:0xf..9, privKey:c..5
    Solana:
    address:A…k, privKey:5…a
    address:D…s, privKey:3…s
    address:G…5, privKey:M…u
    ```



#### Import

- **Single chain** 

  - Single wallet

    ```
    /// Solana
    let importer = FGHDWalletUniGenerator.solana()
    let importedWallet = try importer.importWallet(from: wallet.mnemonic ?? "")
    
    /// EVM
    let importer = FGHDWalletUniGenerator.evm()
    let importedWallet = try importer.importWallet(from: wallet.mnemonic ?? "")
    ```

  - Batch wallets

    ```
    /// Solana
    let importer = FGHDWalletUniGenerator.evm()
    let importedWallet = try importer.importWallets(from: wallet.mnemonic ?? "")
    
    /// EVM
    let importer = FGHDWalletUniImporter.evm()
    let importedWalltes = try await importer.importWallets(from: phrases)
    ```

    

- **Multi-chain**

  - Single wallet

    ```
    // Set up all needed EVM chains except Ethereum which will be contained by default.
    let evmSharedChains: [any FGChainDescribable.Type] = [FGBaseChain.self, FGOptimismChain.self]
    
    // Configure the importing handler of each chain.
    let handlers = [FGHDImportHandler.solana(),
                    FGHDImportHandler.evm(serviceSharedChains: evmSharedChains)]
    let importer = FGMultiChainWalletProcessor(chainHandlers: handlers)
    
    let mnemomic = "xx yy cc ..."
    
    // Using single(mnemonic:) as the import operation kind.
    let genWalletsMap = try await importer.process(with: .singleSpecific(mnemomic: mnemomic))
    
    for (chainName, wallets) in walletMap {
       print("\(chainName):")
        for wallet in wallets {
            print("address:\(wallet.address), privKey:\(wallet.privKey), mnemonic:\(wallet.mnemonic)")
        }
    }
    
    /// Output ///
    Optimism:
    address:0x1…1, privKey:9…8
    Ethereum:
    address:0x1…1, privKey:9…8
    Base:
    address:0x1…1, privKey:9…8
    Solana:
    address:A…k, privKey:5…a
    ```

  - Batch wallets

    ```
    // Set up all needed EVM chains except Ethereum which will be contained by default.
    let evmSharedChains: [any FGChainDescribable.Type] = [FGBaseChain.self, FGOptimismChain.self]
    
    // Configure the importing handler of each chain.
    let handlers = [FGHDImportHandler.solana(),
                    FGHDImportHandler.evm(serviceSharedChains: evmSharedChains)]
    let importer = FGMultiChainWalletProcessor(chainHandlers: handlers)
    
    let mnemonics = ["mnemonic1", "mnemonic2", ... ,"mnemonicN"]
    
    // Using single(mnemonic:) as the import operation kind.
    let genWalletsMap = try await importer.process(with: .batch(mnemonics: mnemonics))
    
    for (chainName, wallets) in walletMap {
       print("\(chainName):")
        for wallet in wallets {
            print("address:\(wallet.address), privKey:\(wallet.privKey), mnemonic:\(wallet.mnemonic)")
        }
    }
    ```



### Export

- **Pasteboard**

  - Single wallet

    ```
     let importer = FGPasteboardExporter()
     let result = try await importer.export(wallet: wallet)
     if !result {
        // Execute the failure logic.
     }
    ```

  - Batch wallets

    ```
     let importer = FGPasteboardExporter()
     let result = try await importer.export(wallets: wallets)
     if !result {
        // Execute the failure logic.
     }
    ```

    Now, the FGPasteboardExporter only supports export 200 wallets and will throw a error if exceeds 200.

  

- **File**

  ```
   // Not sepecify the file path to export. Internally will create automaticlly a file in the  //'Wallet_yyyyMMdd_HHmmss' name format if not set.
   let exporter = FGFileExporter()
   let isSuccess = try await exporter.export(wallet: wallet)
   
   // Sepecify the file path to export.
   let filePath = "A detailed path"
    let exporter = FGFileExporter(filePath: filePath)
   let isSuccess = try await exporter.export(wallet: wallet)
  ```

  The  default max count of wallets that can be written at once is 100,  if the total count of wallets to be exported exceeds this value, they will be written one by one, otherwise will be written in one time. It supports be sepecified by set the Initialization param `singleWriteMaxCount`, but should set a reasonable value to avoid OOM.

  ```
  let exporter = FGFileExporter(singleWriteMaxCount: 500)
  ```

Dependencies

  • Bip39.swift: https://github.com/tesseract-one/Bip39.swift.git
  • K1: https://github.com/Sajjon/K1
  • BigInt: https://github.com/attaswift/BigInt.git

####

Package Metadata

Repository: BCSuite/Forging

Stars: 1

Forks: 0

Open issues: 0

Default branch: main

Primary language: swift

Topics: crypto, evm, multichain-wallet, solana, swift, wallet, web3

README: README.md