├── .github ├── FUNDING.yml ├── ISSUE_TEMPLATE │ ├── bug_report.md │ ├── feature_request.md │ └── question.md ├── PULL_REQUEST_TEMPLATE.md └── workflows │ └── swift.yml ├── .gitignore ├── CODE_OF_CONDUCT.md ├── CONTRIBUTING.md ├── Ethereum.podspec ├── Example Apps ├── AppImport │ ├── Package.swift │ ├── README.md │ └── Sources │ │ └── AppImport │ │ └── Example.swift ├── EthereumProject.xcodeproj │ ├── project.pbxproj │ ├── project.xcworkspace │ │ ├── contents.xcworkspacedata │ │ └── xcshareddata │ │ │ ├── IDEWorkspaceChecks.plist │ │ │ └── swiftpm │ │ │ └── Package.resolved │ ├── xcshareddata │ │ └── xcschemes │ │ │ └── iOS App.xcscheme │ └── xcuserdata │ │ ├── ertembiyik.xcuserdatad │ │ └── xcschemes │ │ │ └── xcschememanagement.plist │ │ └── ivanvorobei.xcuserdatad │ │ └── xcschemes │ │ └── xcschememanagement.plist └── iOS App │ ├── App │ └── AppDelegate.swift │ ├── Resources │ ├── Assets.xcassets │ │ ├── AppIcon.appiconset │ │ │ └── Contents.json │ │ └── Contents.json │ ├── Base.lproj │ │ └── LaunchScreen.storyboard │ └── Info.plist │ └── Scenes │ └── RootController.swift ├── LICENSE ├── Package.resolved ├── Package.swift ├── README.md ├── Sources └── Ethereum │ ├── AccountManager.swift │ ├── EthereumService.swift │ ├── Extensions │ ├── Data+Ext.swift │ └── String+Ext.swift │ ├── Models │ ├── Account.swift │ ├── Block.swift │ ├── ConvertingError.swift │ ├── JSONRPC │ │ ├── JSONRPCMethod.swift │ │ ├── JSONRPCRequest.swift │ │ └── JSONRPCResponse.swift │ ├── Log.swift │ ├── Network.swift │ ├── Node.swift │ ├── Receipt.swift │ ├── ResponseError.swift │ ├── Signature.swift │ └── Transaction.swift │ ├── Provider │ ├── Provider.swift │ └── ProviderError.swift │ ├── RLP │ ├── RLPEncodable.swift │ ├── RLPEncoder.swift │ └── RLPError.swift │ ├── SmartContract │ ├── ABI │ │ ├── ABIDecoder.swift │ │ ├── ABIEncodable.swift │ │ ├── ABIEncoder.swift │ │ ├── ABIError.swift │ │ └── ABIEthereumAddress.swift │ ├── ERC20.swift │ ├── ERC721.swift │ ├── SmartContract.swift │ ├── SmartContractMethod.swift │ ├── SmartContractParam.swift │ └── SmartContractValueType.swift │ ├── Storage │ ├── AES.swift │ ├── KeychainStorage.swift │ ├── Storage.swift │ ├── StorageFile.swift │ ├── UserDefaultsStorage.swift │ └── iCloudStorage.swift │ └── Uitils.swift ├── TODO.md └── Tests └── EthereumTests ├── ABI ├── ABIDecoderTests.swift └── ABIEncoderTests.swift ├── AccountManagerTests.swift ├── Contracts ├── ERC20Tests.swift └── ERC721Tests.swift ├── EthereumServiceTests.swift ├── NodeTests.swift ├── ProviderTests.swift ├── Storage Tests └── UserDefaultsStorageTests.swift └── UtilsTests.swift /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | github: [ivanvorobei, sparrowcode] 2 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a report to help us improve 4 | title: '' 5 | labels: bug 6 | assignees: ivanvorobei 7 | --- 8 | 9 | **Details** 10 | - iOS Version [e.g. 15.2] 11 | - Framework Version [e.g. 1.0.2] 12 | - Installed via [e.g. SPM, Cocoapods, Manually] 13 | 14 | **Describe the Bug** 15 | A clear and concise description of what the bug is. 16 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Suggest an idea for this project 4 | title: '' 5 | labels: enhancement 6 | assignees: ivanvorobei 7 | 8 | --- 9 | 10 | **Feature Description** 11 | Describe what functionality you want to see. 12 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/question.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Question 3 | about: Something is not clear with the project 4 | title: '' 5 | labels: question 6 | assignees: ivanvorobei 7 | 8 | --- 9 | 10 | **Describe the Problem** 11 | A clear and concise description of what you want to do. 12 | -------------------------------------------------------------------------------- /.github/PULL_REQUEST_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | ## Goal 2 | 3 | -------------------------------------------------------------------------------- /.github/workflows/swift.yml: -------------------------------------------------------------------------------- 1 | name: Swift 2 | 3 | on: 4 | push: 5 | branches: [ main ] 6 | pull_request: 7 | branches: [ main ] 8 | 9 | jobs: 10 | build: 11 | 12 | runs-on: macos-latest 13 | 14 | steps: 15 | - uses: actions/checkout@v3 16 | - name: Build 17 | run: swift build -v 18 | - name: Run tests 19 | run: swift test -v --parallel 20 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # osX files 2 | .DS_Store 3 | .Trashes 4 | 5 | # Swift Package Manager 6 | .swiftpm 7 | 8 | # User Interface 9 | *UserInterfaceState.xcuserstate 10 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Contributor Covenant Code of Conduct 2 | 3 | ## Our Pledge 4 | 5 | We as members, contributors, and leaders pledge to make participation in our 6 | community a harassment-free experience for everyone, regardless of age, body 7 | size, visible or invisible disability, ethnicity, sex characteristics, gender 8 | identity and expression, level of experience, education, socio-economic status, 9 | nationality, personal appearance, race, religion, or sexual identity 10 | and orientation. 11 | 12 | We pledge to act and interact in ways that contribute to an open, welcoming, 13 | diverse, inclusive, and healthy community. 14 | 15 | ## Our Standards 16 | 17 | Examples of behavior that contributes to a positive environment for our 18 | community include: 19 | 20 | * Demonstrating empathy and kindness toward other people 21 | * Being respectful of differing opinions, viewpoints, and experiences 22 | * Giving and gracefully accepting constructive feedback 23 | * Accepting responsibility and apologizing to those affected by our mistakes, 24 | and learning from the experience 25 | * Focusing on what is best not just for us as individuals, but for the 26 | overall community 27 | 28 | Examples of unacceptable behavior include: 29 | 30 | * The use of sexualized language or imagery, and sexual attention or 31 | advances of any kind 32 | * Trolling, insulting or derogatory comments, and personal or political attacks 33 | * Public or private harassment 34 | * Publishing others' private information, such as a physical or email 35 | address, without their explicit permission 36 | * Other conduct which could reasonably be considered inappropriate in a 37 | professional setting 38 | 39 | ## Enforcement Responsibilities 40 | 41 | Community leaders are responsible for clarifying and enforcing our standards of 42 | acceptable behavior and will take appropriate and fair corrective action in 43 | response to any behavior that they deem inappropriate, threatening, offensive, 44 | or harmful. 45 | 46 | Community leaders have the right and responsibility to remove, edit, or reject 47 | comments, commits, code, wiki edits, issues, and other contributions that are 48 | not aligned to this Code of Conduct, and will communicate reasons for moderation 49 | decisions when appropriate. 50 | 51 | ## Scope 52 | 53 | This Code of Conduct applies within all community spaces, and also applies when 54 | an individual is officially representing the community in public spaces. 55 | Examples of representing our community include using an official e-mail address, 56 | posting via an official social media account, or acting as an appointed 57 | representative at an online or offline event. 58 | 59 | ## Enforcement 60 | 61 | Instances of abusive, harassing, or otherwise unacceptable behavior may be 62 | reported to the community leaders responsible for enforcement at 63 | hello@ivanvorobei.io. 64 | All complaints will be reviewed and investigated promptly and fairly. 65 | 66 | All community leaders are obligated to respect the privacy and security of the 67 | reporter of any incident. 68 | 69 | ## Enforcement Guidelines 70 | 71 | Community leaders will follow these Community Impact Guidelines in determining 72 | the consequences for any action they deem in violation of this Code of Conduct: 73 | 74 | ### 1. Correction 75 | 76 | **Community Impact**: Use of inappropriate language or other behavior deemed 77 | unprofessional or unwelcome in the community. 78 | 79 | **Consequence**: A private, written warning from community leaders, providing 80 | clarity around the nature of the violation and an explanation of why the 81 | behavior was inappropriate. A public apology may be requested. 82 | 83 | ### 2. Warning 84 | 85 | **Community Impact**: A violation through a single incident or series 86 | of actions. 87 | 88 | **Consequence**: A warning with consequences for continued behavior. No 89 | interaction with the people involved, including unsolicited interaction with 90 | those enforcing the Code of Conduct, for a specified period of time. This 91 | includes avoiding interactions in community spaces as well as external channels 92 | like social media. Violating these terms may lead to a temporary or 93 | permanent ban. 94 | 95 | ### 3. Temporary Ban 96 | 97 | **Community Impact**: A serious violation of community standards, including 98 | sustained inappropriate behavior. 99 | 100 | **Consequence**: A temporary ban from any sort of interaction or public 101 | communication with the community for a specified period of time. No public or 102 | private interaction with the people involved, including unsolicited interaction 103 | with those enforcing the Code of Conduct, is allowed during this period. 104 | Violating these terms may lead to a permanent ban. 105 | 106 | ### 4. Permanent Ban 107 | 108 | **Community Impact**: Demonstrating a pattern of violation of community 109 | standards, including sustained inappropriate behavior, harassment of an 110 | individual, or aggression toward or disparagement of classes of individuals. 111 | 112 | **Consequence**: A permanent ban from any sort of public interaction within 113 | the community. 114 | 115 | ## Attribution 116 | 117 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], 118 | version 2.0, available at 119 | https://www.contributor-covenant.org/version/2/0/code_of_conduct.html. 120 | 121 | Community Impact Guidelines were inspired by [Mozilla's code of conduct 122 | enforcement ladder](https://github.com/mozilla/diversity). 123 | 124 | [homepage]: https://www.contributor-covenant.org 125 | 126 | For answers to common questions about this code of conduct, see the FAQ at 127 | https://www.contributor-covenant.org/faq. Translations are available at 128 | https://www.contributor-covenant.org/translations. 129 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing 2 | 3 | Here provided more info about project, contribution process and recomended changes. 4 | Please, read it before pull request or create issue. 5 | 6 | ## Codestyle 7 | 8 | ### Marks 9 | 10 | For clean struct code good is using marks. 11 | 12 | ```swift 13 | class Example { 14 | 15 | // MARK: - Init 16 | 17 | init() {} 18 | } 19 | ``` 20 | 21 | Here you find all which using in project: 22 | 23 | - // MARK: - Init 24 | - // MARK: - Lifecycle 25 | - // MARK: - Layout 26 | - // MARK: - Public 27 | - // MARK: - Private 28 | - // MARK: - Internal 29 | - // MARK: - Models 30 | - // MARK: - Ovveride 31 | 32 | If you can't find valid, add new to codestyle agreements please. Other can be use if class is large and need struct it even without adding to codestyle agreements. 33 | -------------------------------------------------------------------------------- /Ethereum.podspec: -------------------------------------------------------------------------------- 1 | Pod::Spec.new do |s| 2 | 3 | s.name = 'Ethereum' 4 | s.version = '1.0.0' 5 | s.summary = '' 6 | s.homepage = 'https://github.com/sparrowcode/swift-ethereum' 7 | s.source = { :git => 'https://github.com/sparrowcode/swift-ethereum.git', :tag => s.version } 8 | s.license = { :type => 'MIT', :file => "LICENSE" } 9 | s.author = { 'Ivan Vorobei' => 'hello@sparrowcode.io' } 10 | 11 | s.swift_version = '5.1' 12 | s.ios.deployment_target = '13.0' 13 | s.tvos.deployment_target = '13.0' 14 | 15 | s.source_files = 'Sources/Ethereum/**/*.swift' 16 | 17 | end 18 | -------------------------------------------------------------------------------- /Example Apps/AppImport/Package.swift: -------------------------------------------------------------------------------- 1 | // swift-tools-version: 5.4 2 | 3 | import PackageDescription 4 | 5 | let package = Package( 6 | name: "AppImport", 7 | defaultLocalization: "en", 8 | platforms: [ 9 | .iOS(.v13), 10 | ], 11 | products: [ 12 | .library(name: "AppImport", targets: ["AppImport"]), 13 | ], 14 | dependencies: [ 15 | .package(url: "https://github.com/ivanvorobei/SparrowKit", .upToNextMajor(from: "3.5.0")) 16 | ], 17 | targets: [ 18 | .target( 19 | name: "AppImport", 20 | dependencies: [ 21 | .product(name: "SparrowKit", package: "SparrowKit") 22 | ] 23 | ) 24 | ] 25 | ) 26 | -------------------------------------------------------------------------------- /Example Apps/AppImport/README.md: -------------------------------------------------------------------------------- 1 | # AppImport 2 | -------------------------------------------------------------------------------- /Example Apps/AppImport/Sources/AppImport/Example.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | -------------------------------------------------------------------------------- /Example Apps/EthereumProject.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 55; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | F4B18EAE27E4A309005DE566 /* RootController.swift in Sources */ = {isa = PBXBuildFile; fileRef = F4B18EA827E4A309005DE566 /* RootController.swift */; }; 11 | F4B18EAF27E4A309005DE566 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = F4B18EAA27E4A309005DE566 /* Assets.xcassets */; }; 12 | F4B18EB027E4A309005DE566 /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = F4B18EAB27E4A309005DE566 /* LaunchScreen.storyboard */; }; 13 | F4B18EB127E4A309005DE566 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = F4B18EAD27E4A309005DE566 /* AppDelegate.swift */; }; 14 | F4B18EB427E4A3BB005DE566 /* AppImport in Frameworks */ = {isa = PBXBuildFile; productRef = F4B18EB327E4A3BB005DE566 /* AppImport */; }; 15 | /* End PBXBuildFile section */ 16 | 17 | /* Begin PBXFileReference section */ 18 | F4B18E8C27E4A207005DE566 /* iOS App.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "iOS App.app"; sourceTree = BUILT_PRODUCTS_DIR; }; 19 | F4B18E9D27E4A208005DE566 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 20 | F4B18EA827E4A309005DE566 /* RootController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RootController.swift; sourceTree = ""; }; 21 | F4B18EAA27E4A309005DE566 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 22 | F4B18EAC27E4A309005DE566 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; 23 | F4B18EAD27E4A309005DE566 /* AppDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; 24 | F4B18EB227E4A358005DE566 /* AppImport */ = {isa = PBXFileReference; lastKnownFileType = wrapper; path = AppImport; sourceTree = ""; }; 25 | F4FE3B2227E9C4F800E6E0CD /* Ethereum */ = {isa = PBXFileReference; lastKnownFileType = wrapper; name = Ethereum; path = ..; sourceTree = ""; }; 26 | /* End PBXFileReference section */ 27 | 28 | /* Begin PBXFrameworksBuildPhase section */ 29 | F4B18E8927E4A207005DE566 /* Frameworks */ = { 30 | isa = PBXFrameworksBuildPhase; 31 | buildActionMask = 2147483647; 32 | files = ( 33 | F4B18EB427E4A3BB005DE566 /* AppImport in Frameworks */, 34 | ); 35 | runOnlyForDeploymentPostprocessing = 0; 36 | }; 37 | /* End PBXFrameworksBuildPhase section */ 38 | 39 | /* Begin PBXGroup section */ 40 | F4B18E8327E4A207005DE566 = { 41 | isa = PBXGroup; 42 | children = ( 43 | F4B18EB227E4A358005DE566 /* AppImport */, 44 | F4FE3B2227E9C4F800E6E0CD /* Ethereum */, 45 | F4B18E8E27E4A207005DE566 /* iOS App */, 46 | F4B18E8D27E4A207005DE566 /* Products */, 47 | F4B18EA427E4A248005DE566 /* Frameworks */, 48 | ); 49 | sourceTree = ""; 50 | }; 51 | F4B18E8D27E4A207005DE566 /* Products */ = { 52 | isa = PBXGroup; 53 | children = ( 54 | F4B18E8C27E4A207005DE566 /* iOS App.app */, 55 | ); 56 | name = Products; 57 | sourceTree = ""; 58 | }; 59 | F4B18E8E27E4A207005DE566 /* iOS App */ = { 60 | isa = PBXGroup; 61 | children = ( 62 | F4B18EA927E4A309005DE566 /* App */, 63 | F4B18EA727E4A309005DE566 /* Scenes */, 64 | F4B18EB527E4A3CB005DE566 /* Resources */, 65 | ); 66 | path = "iOS App"; 67 | sourceTree = ""; 68 | }; 69 | F4B18EA427E4A248005DE566 /* Frameworks */ = { 70 | isa = PBXGroup; 71 | children = ( 72 | ); 73 | name = Frameworks; 74 | sourceTree = ""; 75 | }; 76 | F4B18EA727E4A309005DE566 /* Scenes */ = { 77 | isa = PBXGroup; 78 | children = ( 79 | F4B18EA827E4A309005DE566 /* RootController.swift */, 80 | ); 81 | path = Scenes; 82 | sourceTree = ""; 83 | }; 84 | F4B18EA927E4A309005DE566 /* App */ = { 85 | isa = PBXGroup; 86 | children = ( 87 | F4B18EAD27E4A309005DE566 /* AppDelegate.swift */, 88 | ); 89 | path = App; 90 | sourceTree = ""; 91 | }; 92 | F4B18EB527E4A3CB005DE566 /* Resources */ = { 93 | isa = PBXGroup; 94 | children = ( 95 | F4B18EAA27E4A309005DE566 /* Assets.xcassets */, 96 | F4B18EAB27E4A309005DE566 /* LaunchScreen.storyboard */, 97 | F4B18E9D27E4A208005DE566 /* Info.plist */, 98 | ); 99 | path = Resources; 100 | sourceTree = ""; 101 | }; 102 | /* End PBXGroup section */ 103 | 104 | /* Begin PBXNativeTarget section */ 105 | F4B18E8B27E4A207005DE566 /* iOS App */ = { 106 | isa = PBXNativeTarget; 107 | buildConfigurationList = F4B18EA027E4A208005DE566 /* Build configuration list for PBXNativeTarget "iOS App" */; 108 | buildPhases = ( 109 | F4B18E8827E4A207005DE566 /* Sources */, 110 | F4B18E8927E4A207005DE566 /* Frameworks */, 111 | F4B18E8A27E4A207005DE566 /* Resources */, 112 | ); 113 | buildRules = ( 114 | ); 115 | dependencies = ( 116 | ); 117 | name = "iOS App"; 118 | packageProductDependencies = ( 119 | F4B18EB327E4A3BB005DE566 /* AppImport */, 120 | ); 121 | productName = Ethereum; 122 | productReference = F4B18E8C27E4A207005DE566 /* iOS App.app */; 123 | productType = "com.apple.product-type.application"; 124 | }; 125 | /* End PBXNativeTarget section */ 126 | 127 | /* Begin PBXProject section */ 128 | F4B18E8427E4A207005DE566 /* Project object */ = { 129 | isa = PBXProject; 130 | attributes = { 131 | BuildIndependentTargetsInParallel = 1; 132 | LastSwiftUpdateCheck = 1330; 133 | LastUpgradeCheck = 1330; 134 | TargetAttributes = { 135 | F4B18E8B27E4A207005DE566 = { 136 | CreatedOnToolsVersion = 13.3; 137 | }; 138 | }; 139 | }; 140 | buildConfigurationList = F4B18E8727E4A207005DE566 /* Build configuration list for PBXProject "EthereumProject" */; 141 | compatibilityVersion = "Xcode 13.0"; 142 | developmentRegion = en; 143 | hasScannedForEncodings = 0; 144 | knownRegions = ( 145 | en, 146 | Base, 147 | ); 148 | mainGroup = F4B18E8327E4A207005DE566; 149 | productRefGroup = F4B18E8D27E4A207005DE566 /* Products */; 150 | projectDirPath = ""; 151 | projectRoot = ""; 152 | targets = ( 153 | F4B18E8B27E4A207005DE566 /* iOS App */, 154 | ); 155 | }; 156 | /* End PBXProject section */ 157 | 158 | /* Begin PBXResourcesBuildPhase section */ 159 | F4B18E8A27E4A207005DE566 /* Resources */ = { 160 | isa = PBXResourcesBuildPhase; 161 | buildActionMask = 2147483647; 162 | files = ( 163 | F4B18EAF27E4A309005DE566 /* Assets.xcassets in Resources */, 164 | F4B18EB027E4A309005DE566 /* LaunchScreen.storyboard in Resources */, 165 | ); 166 | runOnlyForDeploymentPostprocessing = 0; 167 | }; 168 | /* End PBXResourcesBuildPhase section */ 169 | 170 | /* Begin PBXSourcesBuildPhase section */ 171 | F4B18E8827E4A207005DE566 /* Sources */ = { 172 | isa = PBXSourcesBuildPhase; 173 | buildActionMask = 2147483647; 174 | files = ( 175 | F4B18EB127E4A309005DE566 /* AppDelegate.swift in Sources */, 176 | F4B18EAE27E4A309005DE566 /* RootController.swift in Sources */, 177 | ); 178 | runOnlyForDeploymentPostprocessing = 0; 179 | }; 180 | /* End PBXSourcesBuildPhase section */ 181 | 182 | /* Begin PBXVariantGroup section */ 183 | F4B18EAB27E4A309005DE566 /* LaunchScreen.storyboard */ = { 184 | isa = PBXVariantGroup; 185 | children = ( 186 | F4B18EAC27E4A309005DE566 /* Base */, 187 | ); 188 | name = LaunchScreen.storyboard; 189 | sourceTree = ""; 190 | }; 191 | /* End PBXVariantGroup section */ 192 | 193 | /* Begin XCBuildConfiguration section */ 194 | F4B18E9E27E4A208005DE566 /* Debug */ = { 195 | isa = XCBuildConfiguration; 196 | buildSettings = { 197 | ALWAYS_SEARCH_USER_PATHS = NO; 198 | CLANG_ANALYZER_NONNULL = YES; 199 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 200 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++17"; 201 | CLANG_ENABLE_MODULES = YES; 202 | CLANG_ENABLE_OBJC_ARC = YES; 203 | CLANG_ENABLE_OBJC_WEAK = YES; 204 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 205 | CLANG_WARN_BOOL_CONVERSION = YES; 206 | CLANG_WARN_COMMA = YES; 207 | CLANG_WARN_CONSTANT_CONVERSION = YES; 208 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 209 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 210 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 211 | CLANG_WARN_EMPTY_BODY = YES; 212 | CLANG_WARN_ENUM_CONVERSION = YES; 213 | CLANG_WARN_INFINITE_RECURSION = YES; 214 | CLANG_WARN_INT_CONVERSION = YES; 215 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 216 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 217 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 218 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 219 | CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; 220 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 221 | CLANG_WARN_STRICT_PROTOTYPES = YES; 222 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 223 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; 224 | CLANG_WARN_UNREACHABLE_CODE = YES; 225 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 226 | COPY_PHASE_STRIP = NO; 227 | DEBUG_INFORMATION_FORMAT = dwarf; 228 | ENABLE_STRICT_OBJC_MSGSEND = YES; 229 | ENABLE_TESTABILITY = YES; 230 | GCC_C_LANGUAGE_STANDARD = gnu11; 231 | GCC_DYNAMIC_NO_PIC = NO; 232 | GCC_NO_COMMON_BLOCKS = YES; 233 | GCC_OPTIMIZATION_LEVEL = 0; 234 | GCC_PREPROCESSOR_DEFINITIONS = ( 235 | "DEBUG=1", 236 | "$(inherited)", 237 | ); 238 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 239 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 240 | GCC_WARN_UNDECLARED_SELECTOR = YES; 241 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 242 | GCC_WARN_UNUSED_FUNCTION = YES; 243 | GCC_WARN_UNUSED_VARIABLE = YES; 244 | IPHONEOS_DEPLOYMENT_TARGET = 15.4; 245 | MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; 246 | MTL_FAST_MATH = YES; 247 | ONLY_ACTIVE_ARCH = YES; 248 | SDKROOT = iphoneos; 249 | SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; 250 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 251 | }; 252 | name = Debug; 253 | }; 254 | F4B18E9F27E4A208005DE566 /* Release */ = { 255 | isa = XCBuildConfiguration; 256 | buildSettings = { 257 | ALWAYS_SEARCH_USER_PATHS = NO; 258 | CLANG_ANALYZER_NONNULL = YES; 259 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 260 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++17"; 261 | CLANG_ENABLE_MODULES = YES; 262 | CLANG_ENABLE_OBJC_ARC = YES; 263 | CLANG_ENABLE_OBJC_WEAK = YES; 264 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 265 | CLANG_WARN_BOOL_CONVERSION = YES; 266 | CLANG_WARN_COMMA = YES; 267 | CLANG_WARN_CONSTANT_CONVERSION = YES; 268 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 269 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 270 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 271 | CLANG_WARN_EMPTY_BODY = YES; 272 | CLANG_WARN_ENUM_CONVERSION = YES; 273 | CLANG_WARN_INFINITE_RECURSION = YES; 274 | CLANG_WARN_INT_CONVERSION = YES; 275 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 276 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 277 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 278 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 279 | CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; 280 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 281 | CLANG_WARN_STRICT_PROTOTYPES = YES; 282 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 283 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; 284 | CLANG_WARN_UNREACHABLE_CODE = YES; 285 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 286 | COPY_PHASE_STRIP = NO; 287 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 288 | ENABLE_NS_ASSERTIONS = NO; 289 | ENABLE_STRICT_OBJC_MSGSEND = YES; 290 | GCC_C_LANGUAGE_STANDARD = gnu11; 291 | GCC_NO_COMMON_BLOCKS = YES; 292 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 293 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 294 | GCC_WARN_UNDECLARED_SELECTOR = YES; 295 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 296 | GCC_WARN_UNUSED_FUNCTION = YES; 297 | GCC_WARN_UNUSED_VARIABLE = YES; 298 | IPHONEOS_DEPLOYMENT_TARGET = 15.4; 299 | MTL_ENABLE_DEBUG_INFO = NO; 300 | MTL_FAST_MATH = YES; 301 | SDKROOT = iphoneos; 302 | SWIFT_COMPILATION_MODE = wholemodule; 303 | SWIFT_OPTIMIZATION_LEVEL = "-O"; 304 | VALIDATE_PRODUCT = YES; 305 | }; 306 | name = Release; 307 | }; 308 | F4B18EA127E4A208005DE566 /* Debug */ = { 309 | isa = XCBuildConfiguration; 310 | buildSettings = { 311 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 312 | ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = ""; 313 | CODE_SIGN_STYLE = Manual; 314 | CURRENT_PROJECT_VERSION = 1; 315 | DEVELOPMENT_TEAM = ""; 316 | GENERATE_INFOPLIST_FILE = YES; 317 | INFOPLIST_FILE = "iOS App/Resources/Info.plist"; 318 | INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES; 319 | INFOPLIST_KEY_UILaunchStoryboardName = LaunchScreen; 320 | INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; 321 | INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; 322 | LD_RUNPATH_SEARCH_PATHS = ( 323 | "$(inherited)", 324 | "@executable_path/Frameworks", 325 | ); 326 | MARKETING_VERSION = 1.0; 327 | PRODUCT_BUNDLE_IDENTIFIER = by.ivanvorobei.apps.sparrowcode.Ethereum; 328 | PRODUCT_NAME = "$(TARGET_NAME)"; 329 | PROVISIONING_PROFILE_SPECIFIER = ""; 330 | SWIFT_EMIT_LOC_STRINGS = YES; 331 | SWIFT_VERSION = 5.0; 332 | TARGETED_DEVICE_FAMILY = "1,2"; 333 | }; 334 | name = Debug; 335 | }; 336 | F4B18EA227E4A208005DE566 /* Release */ = { 337 | isa = XCBuildConfiguration; 338 | buildSettings = { 339 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 340 | ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = ""; 341 | CODE_SIGN_STYLE = Manual; 342 | CURRENT_PROJECT_VERSION = 1; 343 | DEVELOPMENT_TEAM = ""; 344 | GENERATE_INFOPLIST_FILE = YES; 345 | INFOPLIST_FILE = "iOS App/Resources/Info.plist"; 346 | INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES; 347 | INFOPLIST_KEY_UILaunchStoryboardName = LaunchScreen; 348 | INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; 349 | INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; 350 | LD_RUNPATH_SEARCH_PATHS = ( 351 | "$(inherited)", 352 | "@executable_path/Frameworks", 353 | ); 354 | MARKETING_VERSION = 1.0; 355 | PRODUCT_BUNDLE_IDENTIFIER = by.ivanvorobei.apps.sparrowcode.Ethereum; 356 | PRODUCT_NAME = "$(TARGET_NAME)"; 357 | PROVISIONING_PROFILE_SPECIFIER = ""; 358 | SWIFT_EMIT_LOC_STRINGS = YES; 359 | SWIFT_VERSION = 5.0; 360 | TARGETED_DEVICE_FAMILY = "1,2"; 361 | }; 362 | name = Release; 363 | }; 364 | /* End XCBuildConfiguration section */ 365 | 366 | /* Begin XCConfigurationList section */ 367 | F4B18E8727E4A207005DE566 /* Build configuration list for PBXProject "EthereumProject" */ = { 368 | isa = XCConfigurationList; 369 | buildConfigurations = ( 370 | F4B18E9E27E4A208005DE566 /* Debug */, 371 | F4B18E9F27E4A208005DE566 /* Release */, 372 | ); 373 | defaultConfigurationIsVisible = 0; 374 | defaultConfigurationName = Release; 375 | }; 376 | F4B18EA027E4A208005DE566 /* Build configuration list for PBXNativeTarget "iOS App" */ = { 377 | isa = XCConfigurationList; 378 | buildConfigurations = ( 379 | F4B18EA127E4A208005DE566 /* Debug */, 380 | F4B18EA227E4A208005DE566 /* Release */, 381 | ); 382 | defaultConfigurationIsVisible = 0; 383 | defaultConfigurationName = Release; 384 | }; 385 | /* End XCConfigurationList section */ 386 | 387 | /* Begin XCSwiftPackageProductDependency section */ 388 | F4B18EB327E4A3BB005DE566 /* AppImport */ = { 389 | isa = XCSwiftPackageProductDependency; 390 | productName = AppImport; 391 | }; 392 | /* End XCSwiftPackageProductDependency section */ 393 | }; 394 | rootObject = F4B18E8427E4A207005DE566 /* Project object */; 395 | } 396 | -------------------------------------------------------------------------------- /Example Apps/EthereumProject.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /Example Apps/EthereumProject.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /Example Apps/EthereumProject.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved: -------------------------------------------------------------------------------- 1 | { 2 | "object": { 3 | "pins": [ 4 | { 5 | "package": "SparrowKit", 6 | "repositoryURL": "https://github.com/ivanvorobei/SparrowKit", 7 | "state": { 8 | "branch": null, 9 | "revision": "b3cbe80486f884e872ac3fa055ae0f1063cedbd5", 10 | "version": "3.6.0" 11 | } 12 | } 13 | ] 14 | }, 15 | "version": 1 16 | } 17 | -------------------------------------------------------------------------------- /Example Apps/EthereumProject.xcodeproj/xcshareddata/xcschemes/iOS App.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 32 | 33 | 43 | 45 | 51 | 52 | 53 | 54 | 60 | 62 | 68 | 69 | 70 | 71 | 73 | 74 | 77 | 78 | 79 | -------------------------------------------------------------------------------- /Example Apps/EthereumProject.xcodeproj/xcuserdata/ertembiyik.xcuserdatad/xcschemes/xcschememanagement.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | SchemeUserState 6 | 7 | iOS App.xcscheme_^#shared#^_ 8 | 9 | orderHint 10 | 0 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /Example Apps/EthereumProject.xcodeproj/xcuserdata/ivanvorobei.xcuserdatad/xcschemes/xcschememanagement.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | SchemeUserState 6 | 7 | iOS App.xcscheme_^#shared#^_ 8 | 9 | orderHint 10 | 1 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /Example Apps/iOS App/App/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | // The MIT License (MIT) 2 | // Copyright © 2022 Sparrow Code LTD (hello@sparrowcode.io) 3 | // 4 | // Permission is hereby granted, free of charge, to any person obtaining a copy 5 | // of this software and associated documentation files (the "Software"), to deal 6 | // in the Software without restriction, including without limitation the rights 7 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | // copies of the Software, and to permit persons to whom the Software is 9 | // furnished to do so, subject to the following conditions: 10 | // 11 | // The above copyright notice and this permission notice shall be included in all 12 | // copies or substantial portions of the Software. 13 | // 14 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 20 | // SOFTWARE. 21 | 22 | import UIKit 23 | import SparrowKit 24 | 25 | @UIApplicationMain 26 | class AppDelegate: SPAppWindowDelegate { 27 | 28 | func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { 29 | let rootController = RootController().wrapToNavigationController(prefersLargeTitles: false) 30 | makeKeyAndVisible(viewController: rootController, tint: .systemBlue) 31 | return true 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /Example Apps/iOS App/Resources/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "iphone", 5 | "scale" : "2x", 6 | "size" : "20x20" 7 | }, 8 | { 9 | "idiom" : "iphone", 10 | "scale" : "3x", 11 | "size" : "20x20" 12 | }, 13 | { 14 | "idiom" : "iphone", 15 | "scale" : "2x", 16 | "size" : "29x29" 17 | }, 18 | { 19 | "idiom" : "iphone", 20 | "scale" : "3x", 21 | "size" : "29x29" 22 | }, 23 | { 24 | "idiom" : "iphone", 25 | "scale" : "2x", 26 | "size" : "40x40" 27 | }, 28 | { 29 | "idiom" : "iphone", 30 | "scale" : "3x", 31 | "size" : "40x40" 32 | }, 33 | { 34 | "idiom" : "iphone", 35 | "scale" : "2x", 36 | "size" : "60x60" 37 | }, 38 | { 39 | "idiom" : "iphone", 40 | "scale" : "3x", 41 | "size" : "60x60" 42 | }, 43 | { 44 | "idiom" : "ipad", 45 | "scale" : "1x", 46 | "size" : "20x20" 47 | }, 48 | { 49 | "idiom" : "ipad", 50 | "scale" : "2x", 51 | "size" : "20x20" 52 | }, 53 | { 54 | "idiom" : "ipad", 55 | "scale" : "1x", 56 | "size" : "29x29" 57 | }, 58 | { 59 | "idiom" : "ipad", 60 | "scale" : "2x", 61 | "size" : "29x29" 62 | }, 63 | { 64 | "idiom" : "ipad", 65 | "scale" : "1x", 66 | "size" : "40x40" 67 | }, 68 | { 69 | "idiom" : "ipad", 70 | "scale" : "2x", 71 | "size" : "40x40" 72 | }, 73 | { 74 | "idiom" : "ipad", 75 | "scale" : "1x", 76 | "size" : "76x76" 77 | }, 78 | { 79 | "idiom" : "ipad", 80 | "scale" : "2x", 81 | "size" : "76x76" 82 | }, 83 | { 84 | "idiom" : "ipad", 85 | "scale" : "2x", 86 | "size" : "83.5x83.5" 87 | }, 88 | { 89 | "idiom" : "ios-marketing", 90 | "scale" : "1x", 91 | "size" : "1024x1024" 92 | } 93 | ], 94 | "info" : { 95 | "author" : "xcode", 96 | "version" : 1 97 | } 98 | } 99 | -------------------------------------------------------------------------------- /Example Apps/iOS App/Resources/Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "author" : "xcode", 4 | "version" : 1 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /Example Apps/iOS App/Resources/Base.lproj/LaunchScreen.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /Example Apps/iOS App/Resources/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /Example Apps/iOS App/Scenes/RootController.swift: -------------------------------------------------------------------------------- 1 | // The MIT License (MIT) 2 | // Copyright © 2022 Sparrow Code LTD (hello@sparrowcode.io) 3 | // 4 | // Permission is hereby granted, free of charge, to any person obtaining a copy 5 | // of this software and associated documentation files (the "Software"), to deal 6 | // in the Software without restriction, including without limitation the rights 7 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | // copies of the Software, and to permit persons to whom the Software is 9 | // furnished to do so, subject to the following conditions: 10 | // 11 | // The above copyright notice and this permission notice shall be included in all 12 | // copies or substantial portions of the Software. 13 | // 14 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 20 | // SOFTWARE. 21 | 22 | import UIKit 23 | import SparrowKit 24 | 25 | class RootController: SPController {} 26 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 Sparrow Code 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /Package.resolved: -------------------------------------------------------------------------------- 1 | { 2 | "object": { 3 | "pins": [ 4 | { 5 | "package": "BigInt", 6 | "repositoryURL": "https://github.com/attaswift/BigInt.git", 7 | "state": { 8 | "branch": null, 9 | "revision": "0ed110f7555c34ff468e72e1686e59721f2b0da6", 10 | "version": "5.3.0" 11 | } 12 | }, 13 | { 14 | "package": "secp256k1", 15 | "repositoryURL": "https://github.com/GigaBitcoin/secp256k1.swift.git", 16 | "state": { 17 | "branch": "main", 18 | "revision": "31723af33c3c462ec5b75cc30d6621b23836daff", 19 | "version": null 20 | } 21 | }, 22 | { 23 | "package": "SwiftKeccak", 24 | "repositoryURL": "https://github.com/bitflying/SwiftKeccak.git", 25 | "state": { 26 | "branch": "master", 27 | "revision": "5fc323d4b08a8c77cbb690505445b32a3b5a8aff", 28 | "version": null 29 | } 30 | } 31 | ] 32 | }, 33 | "version": 1 34 | } 35 | -------------------------------------------------------------------------------- /Package.swift: -------------------------------------------------------------------------------- 1 | // swift-tools-version: 5.4 2 | 3 | import PackageDescription 4 | 5 | let package = Package( 6 | name: "Ethereum", 7 | defaultLocalization: "en", 8 | platforms: [ 9 | .iOS(.v13), .macOS(.v10_15), .watchOS(.v6), .tvOS(.v13) 10 | ], 11 | products: [ 12 | .library( 13 | name: "Ethereum", 14 | targets: ["Ethereum"] 15 | ) 16 | ], 17 | dependencies: [ 18 | .package(url: "https://github.com/GigaBitcoin/secp256k1.swift.git", .branchItem("main")), 19 | .package(url: "https://github.com/bitflying/SwiftKeccak.git", .branchItem("master")), 20 | .package(url: "https://github.com/attaswift/BigInt.git", from: "5.3.0") 21 | ], 22 | targets: [ 23 | .target( 24 | name: "Ethereum", 25 | dependencies: [.product(name: "secp256k1", package: "secp256k1.swift"), "SwiftKeccak", "BigInt"] 26 | ), 27 | .testTarget( 28 | name: "EthereumTests", 29 | dependencies: ["Ethereum"] 30 | ) 31 | ] 32 | ) 33 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # swift-ethereum 2 | 3 | Going to be official Ethereum repo for Swift. There is active progress right now. We will soon get the documentation in order and offer examples of use. 4 | 5 | ## Installation 6 | 7 | ### Swift Package Manager 8 | The [Swift Package Manager](https://swift.org/package-manager/) is a tool for automating the distribution of Swift code and is integrated into the `swift` compiler. It’s integrated with the Swift build system to automate the process of downloading, compiling, and linking dependencies. 9 | 10 | Once you have your Swift package set up, adding as a dependency is as easy as adding it to the `dependencies` value of your `Package.swift`. 11 | 12 | ```swift 13 | dependencies: [ 14 | .package(url: "https://github.com/sparrowcode/swift-ethereum.git", .branchItem("main")) 15 | ] 16 | ``` 17 | 18 | ## Navigation 19 | - [Account](#account) 20 | - [Create new account](#create-new-account) 21 | - [Import account](#import-account) 22 | - [Remove account from manager](#remove-account-from-manager) 23 | - [Sign data](#sign-data) 24 | - [Storage](#storage) 25 | - [AES](#to-secure-private-keys-in-a-storage-make-use-of-aes) 26 | - [Encrypting private key](#encrypting-private-key) 27 | - [Decrypting](#decrypting) 28 | - [Interacting with Ethereum](#interacting-with-ethereum) 29 | - [Send a transaction](#send-a-transaction) 30 | - [Call a transaction](#call-a-transaction) 31 | - [Get balance](#get-balance-of-any-address) 32 | - [Get transactions count](#get-transactions-count) 33 | - [Get current block number](#get-current-block-number) 34 | - [Get current gas price](#get-current-gas-price) 35 | - [Get block transaction count by block hash](#get-block-transaction-count-by-block-hash) 36 | - [Get block transaction count by block number](#get-block-transaction-count-by-block-number) 37 | - [Get storage at address](#get-storage-at-address) 38 | - [Get code for address](#get-code-for-address) 39 | - [Get block by hash](#get-block-by-hash) 40 | - [Get block by number](#get-block-by-number) 41 | - [Get transaction by hash](#get-transaction-by-hash) 42 | - [Get uncle by block hash and index](#get-uncle-by-block-hash-and-index) 43 | - [Get uncle by block number and index](#get-uncle-by-block-number-and-index) 44 | - [Get transaction by block hash and index](#get-transaction-by-block-hash-and-index) 45 | - [Get transaction by block number and index](#get-transaction-by-block-number-and-index) 46 | - [Get transaction receipt](#get-transaction-receipt) 47 | - [Estimate gas for transaction](#estimate-gas-for-transaction) 48 | - [Node](#getting-info-about-the-node) 49 | - [Initialize custom node](#initialize-a-node-with-your-custom-rpc-url) 50 | - [Get version](#get-version) 51 | - [Check if listening](#check-if-listening) 52 | - [Get peer count](#get-peer-count) 53 | - [Get client version](#get-client-version) 54 | - [Utils](#utils) 55 | - [Get public key from private key](#get-public-key-from-private-key) 56 | - [Get address from public key](#get-address-from-public-key) 57 | - [Convert Ethereum Units](#convert-ethereum-units) 58 | - [Smart Contracts](#smart-contracts-and-abi-decodingencoding) 59 | - [ERC20](#erc20) 60 | - [Get balance](#get-balance) 61 | - [Transfer tokens](#transfer-tokens) 62 | - [Get decimals](#get-decimals) 63 | - [Get symbol](#get-symbol) 64 | - [Get total supply](#get-total-supply) 65 | - [Get name](#get-name) 66 | - [ERC721](#erc721) 67 | - [Get balance](#get-balance-1) 68 | - [Get owner of](#get-owner-of) 69 | - [Get name](#get-name-1) 70 | - [Get symbol](#get-symbol) 71 | - [Get token URI](#get-token-uri) 72 | - [Get total supply](#get-total-supply-1) 73 | - [Calling Smart Contract transaction and decoding response](#calling-smart-contract-transaction-and-decoding-response) 74 | - [Sending Smart Contract transaction](#sending-smart-contract-transaction) 75 | - [Custom Contracts](#custom-contracts) 76 | 77 | ## Account 78 | 79 | ### Create new account 80 | In order to create a new account you have to use AccountManager. It secures your private key with AES encryption. You can also select a storage, where to hold the encrypted data. 81 | 82 | ```swift 83 | let account = try accountManager.createAccount() 84 | ``` 85 | 86 | ### Import account 87 | ```swift 88 | let storage = UserDefaultsStorage(password: "password") 89 | 90 | let accountManager = AccountManager(storage: storage) 91 | 92 | let account = try accountManager.importAccount(privateKey: "your_private_key") 93 | ``` 94 | 95 | All the fields of your account are decoded from your private key by the library, so after importing your account you can just tap to them: 96 | 97 | ```swift 98 | let address = account.address 99 | let publicKey = account.publicKey 100 | let privateKey = account.privateKey 101 | ``` 102 | 103 | ### Remove account from manager 104 | 105 | ```swift 106 | try accountManager.removeAccount(account) 107 | ``` 108 | 109 | ### Sign data 110 | 111 | You can sign any instance of a type that confirms to `RLPEncodable` with your private key: 112 | 113 | ```swift 114 | let signedData = try account.sign(rlpEncodable) 115 | ``` 116 | 117 | To confirm your type to `RLPEncodable` protocol provide how the data should be encoded for your type in `encodeRLP()` method: 118 | 119 | ```swift 120 | struct SomeType { 121 | let name: String 122 | } 123 | 124 | extension SomeType: RLPEncodable { 125 | func encodeRLP() throws -> Data { 126 | return try RLPEncoder.encode(self.name) 127 | } 128 | } 129 | ``` 130 | 131 | Read more about RLP here: [RLP Ethereum Wiki](https://eth.wiki/fundamentals/rlp) 132 | 133 | ## Storage 134 | 135 | A custom storage can be created by confirming to `StorageProtocol`: 136 | 137 | ```swift 138 | struct CustomStorage: StorageProtocol { 139 | 140 | func storePrivateKey(_ privateKey: String) throws { 141 | // store private key in database of your choice 142 | } 143 | 144 | func getPrivateKey(for address: String) throws -> String { 145 | // get private key from storage for address 146 | } 147 | 148 | func removePrivateKey(for address: String) throws { 149 | // removes private key from storage 150 | } 151 | } 152 | ``` 153 | 154 | ### To secure private keys in a storage make use of `AES` 155 | 156 | #### Encrypting private key: 157 | 158 | ```swift 159 | let privateKey = "some_private_key" 160 | 161 | let aes = AES() 162 | 163 | let iv = aes.initialVector // a vector that is used for encrypting the data 164 | 165 | let aesEncryptedPrivateKey = try aes.encrypt(privateKey, password: password, iv: iv) 166 | ``` 167 | 168 | The encrypt method accepts only the `hexidecimal string of bytes representation` ex: "0xfce353f6616263" or any `Data` 169 | 170 | #### Decrypting: 171 | 172 | ```swift 173 | let decryptedPrivateKey = try aes.decrypt(aesEncryptedPrivateKey, password: password, iv: iv) 174 | ``` 175 | 176 | ## Interacting with Ethereum 177 | 178 | The abstraction between you and Ethereum is `EthereumService`. Before starting to call methods you have to configure provider with a [Node](#initialize-a-node-with-your-custom-rpc-url): 179 | 180 | ```swift 181 | let node = try Node(url: "https://mainnet.infura.io/v3/967cf8dc4a37411c8e62698c7c603cee") 182 | EthereumService.configureProvider(with: node) 183 | ``` 184 | 185 | #### Send a transaction: 186 | 187 | ```swift 188 | let value = "1000000000000000000" // 1 eth in wei 189 | let transaction = try Transaction(from:"0xE92A146f86fEda6D14Ee1dc1BfB620D3F3d1b873", 190 | gasLimit: "210000", 191 | gasPrice: "250000000000", 192 | to: "0xc8DE4C1B4f6F6659944160DaC46B29a330C432B2", 193 | value: BigUInt(value)) 194 | 195 | let transactionHash = try await EthereumService.sendRawTransaction(account: account, transaction: transaction) 196 | ``` 197 | 198 | #### Call a transaction: 199 | 200 | ```swift 201 | let transaction = try Transaction(to: "0xF65FF945f3a6067D0742fD6890f32A6960dD817d", input: "0x") 202 | 203 | let response = try await EthereumService.call(transaction: transaction, block: "latest") 204 | ``` 205 | 206 | > Quick note: block is optional for calling this methods and is set to latest by default 207 | 208 | #### Get balance of any address: 209 | 210 | ```swift 211 | let balance = try await EthereumService.getBalance(for: "address") 212 | ``` 213 | 214 | #### Get transactions count: 215 | 216 | ```swift 217 | let count = try await EthereumService.getTransactionCount(for: "address") 218 | ``` 219 | 220 | #### Get current block number: 221 | 222 | ```swift 223 | let blockNumber = try await EthereumService.blockNumber() 224 | ``` 225 | 226 | #### Get current gas price: 227 | 228 | ```swift 229 | let gasPrice = try await EthereumService.gasPrice() 230 | ``` 231 | 232 | #### Get block transaction count by block hash: 233 | 234 | ```swift 235 | let blockTransactionCount = try await EthereumService.getBlockTransactionCountByHash(blockHash: "block_hash") 236 | ``` 237 | 238 | #### Get block transaction count by block number: 239 | 240 | ```swift 241 | let blockNumber = 2 242 | let blockTransactionCount = try await EthereumService.getBlockTransactionCountByNumber(blockNumber: blockNumber) 243 | ``` 244 | 245 | #### Get storage at address: 246 | 247 | ```swift 248 | let address = "0x295a70b2de5e3953354a6a8344e616ed314d7251" 249 | let storageSlot = 3 250 | 251 | let storage = try await EthereumService.getStorageAt(address: address, storageSlot: storageSlot, block: "latest") 252 | ``` 253 | 254 | #### Get code for address: 255 | 256 | ```swift 257 | let address = "0x2b591e99afE9f32eAA6214f7B7629768c40Eeb39" 258 | 259 | let code = try await EthereumService.getCode(address: address, block: "latest") 260 | ``` 261 | 262 | #### Get block by hash: 263 | 264 | ```swift 265 | let block = try await EthereumService.getBlockByHash(hash: "block_hash") 266 | ``` 267 | 268 | #### Get block by number: 269 | 270 | ```swift 271 | let blockNumber = 12312 272 | 273 | let block = try await EthereumService.getBlockByNumber(blockNumber: blockNumber) 274 | ``` 275 | 276 | #### Get transaction by hash: 277 | 278 | ```swift 279 | let transactionHash = "transaction_hash" 280 | 281 | let transaction = try await EthereumService.getTransactionByHash(transactionHash: transactionHash) 282 | ``` 283 | 284 | #### Get uncle by block hash and index: 285 | 286 | ```swift 287 | let blockHash = "block_hash" 288 | let index = 0 289 | 290 | let uncleBlock = try await EthereumService.getUncleByBlockHashAndIndex(blockHash: blockHash, index: index) 291 | ``` 292 | 293 | #### Get uncle by block number and index: 294 | 295 | ```swift 296 | let blockNumber = 668 297 | let index = 0 298 | 299 | let uncleBlock = try await EthereumService.getUncleByBlockNumberAndIndex(blockNumber: blockNumber, index: index) 300 | ``` 301 | 302 | #### Get transaction by block hash and index: 303 | 304 | ```swift 305 | let blockHash = "block_hash" 306 | let index = 0 307 | 308 | let transaction = try await EthereumService.getTransactionByBlockHashAndIndex(blockHash: blockHash, index: index) 309 | ``` 310 | 311 | #### Get transaction by block number and index: 312 | 313 | ```swift 314 | let blockNumber = 5417326 315 | let index = 0 316 | 317 | let transaction = try await EthereumService.getTransactionByBlockNumberAndIndex(blockNumber: blockNumber, index: index) 318 | ``` 319 | 320 | #### Get transaction receipt: 321 | 322 | ```swift 323 | let transactionHash = "transaction_hash" 324 | 325 | let receipt = try await EthereumService.getTransactionReceipt(transactionHash: transactionHash) 326 | ``` 327 | 328 | #### Estimate gas for transaction: 329 | 330 | ```swift 331 | let transaction = try Transaction(from: "0xE92A146f86fEda6D14Ee1dc1BfB620D3F3d1b873", 332 | to: "0xc8DE4C1B4f6F6659944160DaC46B29a330C432B2", 333 | value: "100000000000") 334 | 335 | let estimatedGas = try await EthereumService.estimateGas(for: transaction) 336 | ``` 337 | 338 | ## Getting info about the Node 339 | 340 | #### Initialize a node with your custom rpc url 341 | ```swift 342 | let node = try Node(url: "your_custom_rpc_url") 343 | ``` 344 | 345 | #### Get version: 346 | 347 | ```swift 348 | let version = try await node.version() 349 | ``` 350 | 351 | #### Check if listening: 352 | 353 | ```swift 354 | let isListening = try await node.listening() 355 | ``` 356 | 357 | #### Get peer count: 358 | 359 | ```swift 360 | let peerCount = try await node.peerCount() 361 | ``` 362 | 363 | #### Get client version: 364 | 365 | ```swift 366 | let clientVersion = try await node.clientVersion() 367 | ``` 368 | 369 | 370 | ## Utils 371 | 372 | We provide commonly used scenarious under an easy interface 373 | 374 | #### Get public key from private key: 375 | 376 | ```swift 377 | let privateKey = "private_key" 378 | 379 | let publicKey = try Utils.KeyUtils.getPublicKey(from: privateKey) 380 | ``` 381 | 382 | #### Get address from public key: 383 | 384 | ```swift 385 | let publicKey = "public_key" 386 | 387 | let ethereumAddress = try Utils.KeyUtils.getEthereumAddress(from: publicKey) 388 | ``` 389 | 390 | #### Convert Ethereum Units: 391 | 392 | ```swift 393 | let wei = "12345678901234567890" 394 | let eth = Utils.Converter.convert(value: wei, from: .wei, to: .eth) 395 | ``` 396 | 397 | ## Smart Contracts and ABI Decoding/Encoding 398 | 399 | We decided to create a transaction based flow for interacting with smart contracts, because of scalable architecture and lack of strong relations 400 | 401 | The flow is super easy, we provide factory for both ERC20 and ERC721 contracts. Factory lets you generate transactions and then you can [call or send them via EthereumService](#calling-smart-contract-transaction-and-decoding-response) 402 | 403 | ### ERC20 404 | 405 | ```swift 406 | let contractAddress = "erc20_contract_address" 407 | ``` 408 | 409 | #### Get balance: 410 | 411 | ```swift 412 | let address = "address_to_check_balance" 413 | 414 | let transaction = try ERC20TransactionFactory.generateBalanceTransaction(address: address, contractAddress: contractAddress) 415 | ``` 416 | 417 | #### Transfer tokens: 418 | 419 | ```swift 420 | let value = BigUInt(some_value) 421 | let toAddress = "to_address" 422 | let gasLimit = BigUInt(gas_limit_value) 423 | let gasPrice = BigUInt(gas_price_value) 424 | 425 | let transaction = try ERC20TransactionFactory.generateTransferTransaction(value: value, 426 | to: toAddress, 427 | gasLimit: gasLimit, 428 | gasPrice: gasPrice, 429 | contractAddress: contractAddress) 430 | ``` 431 | 432 | #### Get decimals: 433 | 434 | ```swift 435 | let transaction = try ERC20TransactionFactory.generateDecimalsTransaction(contractAddress: contractAddress) 436 | ``` 437 | 438 | #### Get symbol: 439 | 440 | ```swift 441 | let transaction = try ERC20TransactionFactory.generateSymbolTransaction(contractAddress: contractAddress) 442 | ``` 443 | 444 | #### Get total supply: 445 | 446 | ```swift 447 | let transaction = try ERC20TransactionFactory.generateTotalSupplyTransaction(contractAddress: contractAddress) 448 | ``` 449 | 450 | #### Get name: 451 | 452 | ```swift 453 | let transaction = try ERC20TransactionFactory.generateNameTransaction(contractAddress: contractAddress) 454 | ``` 455 | 456 | ### ERC721 457 | 458 | ```swift 459 | let contractAddress = "erc721_contract_address" 460 | ``` 461 | 462 | #### Get balance: 463 | 464 | ```swift 465 | let transaction = try ERC721TransactionFactory.generateBalanceTransaction(address: address, contractAddress: contractAddress) 466 | ``` 467 | 468 | #### Get owner of: 469 | 470 | ```swift 471 | let tokenId = BigUInt(708) 472 | 473 | let transaction = try ERC721TransactionFactory.generateOwnerOfTransaction(tokenId: tokenId, contractAddress: contractAddress) 474 | ``` 475 | 476 | #### Get name: 477 | 478 | ```swift 479 | let transaction = try ERC721TransactionFactory.generateNameTransaction(contractAddress: contractAddress) 480 | ``` 481 | 482 | #### Get symbol: 483 | 484 | ```swift 485 | let transaction = try ERC721TransactionFactory.generateSymbolTransaction(contractAddress: contractAddress) 486 | ``` 487 | 488 | #### Get token URI: 489 | 490 | ```swift 491 | let tokenId = BigUInt(708) 492 | 493 | let transaction = try ERC721TransactionFactory.generateTokenURITransaction(tokenId: tokenId, contractAddress: contractAddress) 494 | ``` 495 | 496 | #### Get total supply: 497 | 498 | ```swift 499 | let transaction = try ERC721TransactionFactory.generateTotalSupplyTransaction(contractAddress: contractAddress) 500 | ``` 501 | 502 | #### Calling Smart Contract transaction and decoding response: 503 | 504 | Then just call the transaction with EthereumService, get the abi encoded result and decode it using ABIDecoder: 505 | 506 | ```swift 507 | // transaction is a erc20 balance one 508 | let abiEncodedBalance = try await EthereumService.call(transaction: transaction) 509 | 510 | let balance = try ABIDecoder.decode(abiEncodedBalance, to: .uint()) as? BigUInt 511 | ``` 512 | 513 | If the abi encoded result contains several types, provide them as an array: 514 | 515 | ```swift 516 | let decodedResult = try ABIDecoder.decode(someEncodedValue, to: [.uint(), .string, .address]) 517 | ``` 518 | 519 | > Decode method accepts both Data and String values 520 | 521 | #### Sending Smart Contract transaction: 522 | 523 | If the transaction is a transfer one, send it via EthereumService: 524 | 525 | ```swift 526 | let transactionHash = try await EthereumService.sendRawTransaction(account: account, transaction: transaction) 527 | ``` 528 | 529 | ### Custom Contracts 530 | 531 | If you have a custom contract that you want to interact with, the flow is again very intuitive: 532 | 533 | 1. Select a method that you want to call from your contract: 534 | 535 | For example we want to call this method: 536 | 537 | ``` 538 | function balanceOf(address _owner) constant returns (uint balance); 539 | ``` 540 | 541 | 2. Check the input and output parameters of the method: 542 | 543 | Method accepts `address` type as an input and returns a `uint` 544 | 545 | 3. Encode parameters: 546 | 547 | ```swift 548 | let params = [SmartContractParam(type: .address, value: ABIEthereumAddress("some_address"))] 549 | 550 | let method = SmartContractMethod(name: "balanceOf", params: params) 551 | 552 | guard let data = method.abiData else { 553 | return 554 | } 555 | ``` 556 | 557 | 4. Create a transaction: 558 | 559 | ```swift 560 | let contractAddress = "contract_address" 561 | 562 | let transaction = try Transaction(input: data, to: contractAddress) 563 | ``` 564 | 565 | That's all, next you can [call the transaction and decode the response](#calling-smart-contract-transaction-and-decoding-response) 566 | -------------------------------------------------------------------------------- /Sources/Ethereum/AccountManager.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | public class AccountManager { 4 | 5 | private let storage: StorageProtocol 6 | 7 | public init(storage: StorageProtocol) { 8 | self.storage = storage 9 | } 10 | 11 | public func createAccount() throws -> Account { 12 | 13 | let randomBytes = Data(0..<32).map { _ in UInt32.random(in: UInt32.min...UInt32.max) } 14 | 15 | let privateKeyData = Data(bytes: randomBytes, count: 32) 16 | 17 | let privateKey = String(bytes: privateKeyData) 18 | 19 | try storage.storePrivateKey(privateKey) 20 | 21 | return try Account(privateKey: privateKey) 22 | } 23 | 24 | public func importAccount(privateKey: String) throws -> Account { 25 | 26 | try storage.storePrivateKey(privateKey) 27 | 28 | return try Account(privateKey: privateKey.removeHexPrefix()) 29 | } 30 | 31 | public func removeAccount(_ account: Account) throws { 32 | try storage.removePrivateKey(for: account.address) 33 | } 34 | 35 | } 36 | -------------------------------------------------------------------------------- /Sources/Ethereum/EthereumService.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | import BigInt 3 | 4 | #if canImport(FoundationNetworking) 5 | import FoundationNetworking 6 | #endif 7 | 8 | public enum EthereumService { 9 | 10 | private static var provider: Provider! 11 | 12 | public static func configureProvider(with node: Node) { 13 | self.provider = Provider(node: node) 14 | } 15 | 16 | // MARK: - Original Methods 17 | /** 18 | Ethereum: Returns the current price per gas in wei. 19 | */ 20 | public static func gasPrice(completion: @escaping (Result) -> ()) { 21 | 22 | let params = [String]() 23 | 24 | provider.sendRequest(method: .gasPrice, params: params, decodeTo: String.self) { result in 25 | 26 | switch result { 27 | case .success(let hexGasPrice): 28 | if let gasPrice = BigUInt(hexGasPrice.removeHexPrefix(), radix: 16) { 29 | completion(.success(gasPrice)) 30 | } else { 31 | completion(.failure(ConvertingError.errorConvertingFromHex)) 32 | } 33 | case .failure(let error): 34 | completion(.failure(error)) 35 | } 36 | } 37 | } 38 | 39 | /** 40 | Ethereum: Returns the number of most recent block. 41 | */ 42 | public static func blockNumber(completion: @escaping (Result) -> ()) { 43 | 44 | let params = [String]() 45 | 46 | provider.sendRequest(method: .blockNumber, params: params, decodeTo: String.self) { result in 47 | 48 | switch result { 49 | case .success(let hexBlockNumber): 50 | if let blockNumber = Int(hexBlockNumber.removeHexPrefix(), radix: 16) { 51 | completion(.success(blockNumber)) 52 | } else { 53 | completion(.failure(ConvertingError.errorConvertingFromHex)) 54 | } 55 | case .failure(let error): 56 | completion(.failure(error)) 57 | } 58 | } 59 | } 60 | 61 | /** 62 | Ethereum: Returns the balance of the account of given address. 63 | */ 64 | public static func getBalance(for address: String, block: String = "latest", completion: @escaping (Result) -> ()) { 65 | 66 | let params = [address, block] 67 | 68 | provider.sendRequest(method: .getBalance, params: params, decodeTo: String.self) { result in 69 | 70 | switch result { 71 | case .success(let hexBalance): 72 | if let balance = BigUInt(hexBalance.removeHexPrefix(), radix: 16) { 73 | completion(.success(balance)) 74 | } else { 75 | completion(.failure(ConvertingError.errorConvertingFromHex)) 76 | } 77 | case .failure(let error): 78 | completion(.failure(error)) 79 | } 80 | } 81 | } 82 | 83 | /** 84 | Ethereum: retrieves raw state storage for a smart contract; 85 | */ 86 | public static func getStorageAt(address: String, storageSlot: Int, block: String = "latest", completion: @escaping (Result) -> ()) { 87 | 88 | let hexStorageSlot = String(storageSlot, radix: 16).addHexPrefix() 89 | 90 | let params = [address, hexStorageSlot, block] 91 | 92 | provider.sendRequest(method: .getStorageAt, params: params, decodeTo: String.self) { result in 93 | 94 | switch result { 95 | case .success(let hexValue): 96 | if let value = BigUInt(hexValue.removeHexPrefix(), radix: 16) { 97 | completion(.success(value)) 98 | } else { 99 | completion(.failure(ConvertingError.errorConvertingFromHex)) 100 | } 101 | case .failure(let error): 102 | completion(.failure(error)) 103 | } 104 | } 105 | } 106 | 107 | /** 108 | Ethereum: Returns the number of transactions sent from an address. 109 | */ 110 | public static func getTransactionCount(for address: String, block: String = "latest", completion: @escaping (Result) -> ()) { 111 | 112 | let params = [address, block] 113 | 114 | provider.sendRequest(method: .getTransactionCount, params: params, decodeTo: String.self) { result in 115 | 116 | switch result { 117 | case .success(let hexNonce): 118 | if let nonce = Int(hexNonce.removeHexPrefix(), radix: 16) { 119 | completion(.success(nonce)) 120 | } else { 121 | completion(.failure(ConvertingError.errorConvertingFromHex)) 122 | } 123 | case .failure(let error): 124 | completion(.failure(error)) 125 | } 126 | } 127 | } 128 | 129 | /** 130 | Ethereum: Returns the number of transactions in a block from a block matching the given block hash. 131 | */ 132 | public static func getBlockTransactionCountByHash(blockHash: String, completion: @escaping (Result) -> ()) { 133 | 134 | let params = [blockHash] 135 | 136 | provider.sendRequest(method: .getBlockTransactionCountByHash, params: params, decodeTo: String?.self) { result in 137 | 138 | switch result { 139 | case .success(let hexTransactionCount): 140 | guard let hexTransactionCount = hexTransactionCount else { 141 | completion(.failure(ResponseError.nilResponse)) 142 | return 143 | } 144 | if let transactionCount = Int(hexTransactionCount.removeHexPrefix(), radix: 16) { 145 | completion(.success(transactionCount)) 146 | } else { 147 | completion(.failure(ConvertingError.errorConvertingFromHex)) 148 | } 149 | case .failure(let error): 150 | completion(.failure(error)) 151 | } 152 | } 153 | } 154 | 155 | /** 156 | Ethereum: Returns the number of transactions in a block matching the given block number. 157 | */ 158 | public static func getBlockTransactionCountByNumber(blockNumber: Int, completion: @escaping (Result) -> ()) { 159 | 160 | let hexBlockNumber = String(blockNumber, radix: 16).addHexPrefix() 161 | 162 | let params = [hexBlockNumber] 163 | 164 | provider.sendRequest(method: .getBlockTransactionCountByNumber, params: params, decodeTo: String?.self) { result in 165 | 166 | switch result { 167 | case .success(let hexTransactionCount): 168 | guard let hexTransactionCount = hexTransactionCount else { 169 | completion(.failure(ResponseError.nilResponse)) 170 | return 171 | } 172 | if let transactionCount = Int(hexTransactionCount.removeHexPrefix(), radix: 16) { 173 | completion(.success(transactionCount)) 174 | } else { 175 | completion(.failure(ConvertingError.errorConvertingFromHex)) 176 | } 177 | case .failure(let error): 178 | completion(.failure(error)) 179 | } 180 | } 181 | } 182 | 183 | /** 184 | Ethereum: Returns the compiled solidity code 185 | */ 186 | public static func getCode(address: String, block: String = "latest", completion: @escaping (Result) -> ()) { 187 | 188 | let params = [address, block] 189 | 190 | provider.sendRequest(method: .getCode, params: params, decodeTo: String.self) { result in 191 | 192 | completion(result) 193 | } 194 | } 195 | 196 | /** 197 | Ethereum: Creates new message call transaction or a contract creation for signed transactions. 198 | sendRawTransaction() requires that the transaction be already signed and serialized. So it requires extra serialization steps to use, but enables you to broadcast transactions on hosted nodes. There are other reasons that you might want to use a local key, of course. All of them would require using sendRawTransaction(). 199 | */ 200 | public static func sendRawTransaction(account: Account, transaction: Transaction, completion: @escaping (Result) -> ()) { 201 | 202 | self.getTransactionCount(for: account.address) { result in 203 | 204 | var nonce: Int 205 | 206 | switch result { 207 | case .success(let nonceValue): 208 | nonce = nonceValue 209 | case .failure(let error): 210 | completion(.failure(error)) 211 | return 212 | } 213 | 214 | var unsignedTransaction = transaction 215 | 216 | unsignedTransaction.chainID = provider.node.network.chainID 217 | 218 | unsignedTransaction.nonce = nonce 219 | 220 | var rlpBytes = Data() 221 | 222 | do { 223 | let signedTransaction = try account.sign(transaction: unsignedTransaction) 224 | rlpBytes = try signedTransaction.encodeRLP() 225 | } catch { 226 | completion(.failure(error)) 227 | return 228 | } 229 | 230 | 231 | let signedTransactionHash = String(bytes: rlpBytes).addHexPrefix() 232 | 233 | let params = [signedTransactionHash] 234 | 235 | provider.sendRequest(method: .sendRawTransaction, params: params, decodeTo: String.self) { result in 236 | 237 | completion(result) 238 | } 239 | } 240 | } 241 | 242 | /** 243 | Ethereum: Executes a new message call immediately without creating a transaction on the block chain. Primarily is used on smart contracts 244 | */ 245 | public static func call(transaction: Transaction, block: String = "latest", completion: @escaping (Result) -> ()) { 246 | 247 | 248 | struct Params: Codable { 249 | let transaction: Transaction 250 | let block: String 251 | 252 | func encode(to encoder: Encoder) throws { 253 | var container = encoder.unkeyedContainer() 254 | try container.encode(transaction) 255 | try container.encode(block) 256 | } 257 | } 258 | 259 | let params = Params(transaction: transaction, block: block) 260 | 261 | provider.sendRequest(method: .call, params: params, decodeTo: String.self) { result in 262 | 263 | completion(result) 264 | } 265 | } 266 | 267 | /** 268 | Ethereum: Generates and returns an estimate of how much gas is necessary to allow the transaction to complete. The transaction will not be added to the blockchain. Note that the estimate may be significantly more than the amount of gas actually used by the transaction, for a variety of reasons including EVM mechanics and node performance. 269 | */ 270 | public static func estimateGas(for transaction: Transaction, block: String = "latest", completion: @escaping (Result) -> ()) { 271 | 272 | struct Params: Codable { 273 | let transaction: Transaction 274 | let block: String 275 | 276 | func encode(to encoder: Encoder) throws { 277 | var container = encoder.unkeyedContainer() 278 | try container.encode(transaction) 279 | try container.encode(block) 280 | } 281 | } 282 | 283 | let params = Params(transaction: transaction, block: block) 284 | 285 | provider.sendRequest(method: .estimateGas, params: params, decodeTo: String.self) { result in 286 | 287 | switch result { 288 | case .success(let hexValue): 289 | if let value = BigUInt(hexValue.removeHexPrefix(), radix: 16) { 290 | completion(.success(value)) 291 | } else { 292 | completion(.failure(ConvertingError.errorConvertingFromHex)) 293 | } 294 | case .failure(let error): 295 | completion(.failure(error)) 296 | } 297 | } 298 | } 299 | 300 | /** 301 | Ethereum: Returns information about a block by hash. 302 | */ 303 | public static func getBlockByHash(hash: String, completion: @escaping (Result) -> ()) { 304 | 305 | struct Params: Codable { 306 | let hash: String 307 | let isHydratedTransaction: Bool 308 | 309 | func encode(to encoder: Encoder) throws { 310 | var container = encoder.unkeyedContainer() 311 | try container.encode(hash) 312 | try container.encode(isHydratedTransaction) 313 | } 314 | } 315 | 316 | let params = Params(hash: hash, isHydratedTransaction: false) 317 | 318 | provider.sendRequest(method: .getBlockByHash, params: params, decodeTo: Block.self) { result in 319 | 320 | completion(result) 321 | } 322 | } 323 | 324 | /** 325 | Ethereum: Returns information about a block by block number. 326 | */ 327 | public static func getBlockByNumber(blockNumber: Int, completion: @escaping (Result) -> ()) { 328 | 329 | let hexBlockNumber = String(blockNumber, radix: 16).addHexPrefix() 330 | 331 | struct Params: Codable { 332 | let hexBlockNumber: String 333 | let isHydratedTransaction: Bool 334 | 335 | func encode(to encoder: Encoder) throws { 336 | var container = encoder.unkeyedContainer() 337 | try container.encode(hexBlockNumber) 338 | try container.encode(isHydratedTransaction) 339 | } 340 | } 341 | 342 | let params = Params(hexBlockNumber: hexBlockNumber, isHydratedTransaction: false) 343 | 344 | provider.sendRequest(method: .getBlockByNumber, params: params, decodeTo: Block.self) { result in 345 | 346 | completion(result) 347 | } 348 | } 349 | 350 | /** 351 | Ethereum: Returns the information about a transaction requested by transaction hash. 352 | */ 353 | public static func getTransactionByHash(transactionHash: String, completion: @escaping (Result) -> ()) { 354 | 355 | let params = [transactionHash] 356 | 357 | provider.sendRequest(method: .getTransactionByHash, params: params, decodeTo: Transaction.self) { result in 358 | 359 | completion(result) 360 | } 361 | } 362 | 363 | /** 364 | Ethereum: Returns information about a transaction by block hash and transaction index position. 365 | */ 366 | public static func getTransactionByBlockHashAndIndex(blockHash: String, index: Int, completion: @escaping (Result) -> ()) { 367 | 368 | let hexIndex = String(index, radix: 16).addHexPrefix() 369 | 370 | let params = [blockHash, hexIndex] 371 | 372 | provider.sendRequest(method: .getTransactionByBlockHashAndIndex, params: params, decodeTo: Transaction?.self) { result in 373 | 374 | switch result { 375 | case .success(let optionalTransaction): 376 | guard let transaction = optionalTransaction else { 377 | completion(.failure(ResponseError.noResult)) 378 | return 379 | } 380 | completion(.success(transaction)) 381 | case .failure(let error): 382 | completion(.failure(error)) 383 | } 384 | } 385 | } 386 | 387 | /** 388 | Ethereum: Returns information about a transaction by block number and transaction index position. 389 | */ 390 | public static func getTransactionByBlockNumberAndIndex(blockNumber: Int, index: Int, completion: @escaping (Result) -> ()) { 391 | 392 | let hexBlockNumber = String(blockNumber, radix: 16).addHexPrefix() 393 | 394 | let hexIndex = String(index, radix: 16).addHexPrefix() 395 | 396 | let params = [hexBlockNumber, hexIndex] 397 | 398 | provider.sendRequest(method: .getTransactionByBlockNumberAndIndex, params: params, decodeTo: Transaction?.self) { result in 399 | 400 | switch result { 401 | case .success(let optionalTransaction): 402 | guard let transaction = optionalTransaction else { 403 | completion(.failure(ResponseError.noResult)) 404 | return 405 | } 406 | completion(.success(transaction)) 407 | case .failure(let error): 408 | completion(.failure(error)) 409 | } 410 | } 411 | 412 | } 413 | 414 | /** 415 | Ethereum: Returns the receipt of a transaction by transaction hash. 416 | */ 417 | public static func getTransactionReceipt(transactionHash: String, completion: @escaping (Result) -> ()) { 418 | 419 | let params = [transactionHash] 420 | 421 | provider.sendRequest(method: .getTransactionReceipt, params: params, decodeTo: Receipt.self) { result in 422 | 423 | completion(result) 424 | } 425 | } 426 | 427 | /** 428 | Ethereum: Returns information about a uncle of a block by hash and uncle index position. 429 | */ 430 | public static func getUncleByBlockHashAndIndex(blockHash: String, index: Int, completion: @escaping (Result) -> ()) { 431 | 432 | let hexIndex = String(index, radix: 16).addHexPrefix() 433 | 434 | let params = [blockHash, hexIndex] 435 | 436 | provider.sendRequest(method: .getUncleByBlockHashAndIndex, params: params, decodeTo: Block?.self) { result in 437 | 438 | switch result { 439 | case .success(let optionalBlock): 440 | guard let block = optionalBlock else { 441 | completion(.failure(ResponseError.noResult)) 442 | return 443 | } 444 | completion(.success(block)) 445 | case .failure(let error): 446 | completion(.failure(error)) 447 | } 448 | } 449 | } 450 | 451 | /** 452 | Ethereum: Returns information about a uncle of a block by number and uncle index position. 453 | */ 454 | public static func getUncleByBlockNumberAndIndex(blockNumber: Int, index: Int, completion: @escaping (Result) -> ()) { 455 | 456 | let hexBlockNumber = String(blockNumber, radix: 16).addHexPrefix() 457 | 458 | let hexIndex = String(index, radix: 16).addHexPrefix() 459 | 460 | let params = [hexBlockNumber, hexIndex] 461 | 462 | provider.sendRequest(method: .getUncleByBlockNumberAndIndex, params: params, decodeTo: Block?.self) { result in 463 | 464 | switch result { 465 | case .success(let optionalBlock): 466 | guard let block = optionalBlock else { 467 | completion(.failure(ResponseError.noResult)) 468 | return 469 | } 470 | completion(.success(block)) 471 | case .failure(let error): 472 | completion(.failure(error)) 473 | } 474 | } 475 | } 476 | 477 | // MARK: TO LATER 478 | /** 479 | Ethereum: Returns an array of all logs matching a given filter object. 480 | */ 481 | // public static func getLogs() { 482 | // 483 | // } 484 | /* 485 | /** 486 | Ethereum: Creates a filter object, based on filter options, to notify when the state changes (logs). To check if the state has changed, call eth_getFilterChanges. 487 | */ 488 | public static func newFilter() { 489 | 490 | } 491 | 492 | /** 493 | Ethereum: Creates a filter in the node, to notify when a new block arrives. To check if the state has changed, call eth_getFilterChanges. 494 | */ 495 | public static func newBlockFilter() { 496 | 497 | } 498 | 499 | /** 500 | Ethereum: Creates a filter in the node, to notify when new pending transactions arrive. To check if the state has changed, call eth_getFilterChanges. 501 | */ 502 | public static func newPendingTransactionFilter() { 503 | 504 | } 505 | 506 | /** 507 | Ethereum: Uninstalls a filter with given id. Should always be called when watch is no longer needed. Additonally Filters timeout when they aren't requested with eth_getFilterChanges for a period of time. 508 | */ 509 | public static func uninstallFilter() { 510 | 511 | } 512 | 513 | /** 514 | Ethereum: Polling method for a filter, which returns an array of logs which occurred since last poll. 515 | */ 516 | public static func getFilterChanges() { 517 | 518 | }*/ 519 | } 520 | 521 | extension EthereumService { 522 | 523 | public static func gasPrice() async throws -> BigUInt { 524 | return try await withCheckedThrowingContinuation { continuation in 525 | gasPrice() { result in 526 | switch result { 527 | case .success(let value): 528 | continuation.resume(returning: value) 529 | case .failure(let error): 530 | continuation.resume(throwing: error) 531 | } 532 | } 533 | } 534 | } 535 | 536 | public static func blockNumber() async throws -> Int { 537 | return try await withCheckedThrowingContinuation { continuation in 538 | blockNumber() { result in 539 | switch result { 540 | case .success(let value): 541 | continuation.resume(returning: value) 542 | case .failure(let error): 543 | continuation.resume(throwing: error) 544 | } 545 | } 546 | } 547 | } 548 | 549 | public static func getBalance(for address: String, block: String = "latest") async throws -> BigUInt { 550 | return try await withCheckedThrowingContinuation { continuation in 551 | getBalance(for: address, block: block) { result in 552 | switch result { 553 | case .success(let value): 554 | continuation.resume(returning: value) 555 | case .failure(let error): 556 | continuation.resume(throwing: error) 557 | } 558 | } 559 | } 560 | } 561 | 562 | public static func getStorageAt(address: String, storageSlot: Int, block: String = "latest") async throws -> BigUInt { 563 | return try await withCheckedThrowingContinuation { continuation in 564 | getStorageAt(address: address, storageSlot: storageSlot) { result in 565 | switch result { 566 | case .success(let value): 567 | continuation.resume(returning: value) 568 | case .failure(let error): 569 | continuation.resume(throwing: error) 570 | } 571 | } 572 | } 573 | } 574 | 575 | public static func getTransactionCount(for address: String, block: String = "latest") async throws -> Int { 576 | return try await withCheckedThrowingContinuation { continuation in 577 | getTransactionCount(for: address, block: block) { result in 578 | switch result { 579 | case .success(let value): 580 | continuation.resume(returning: value) 581 | case .failure(let error): 582 | continuation.resume(throwing: error) 583 | } 584 | } 585 | } 586 | } 587 | 588 | public static func getBlockTransactionCountByHash(blockHash: String) async throws -> Int { 589 | return try await withCheckedThrowingContinuation { continuation in 590 | getBlockTransactionCountByHash(blockHash: blockHash) { result in 591 | switch result { 592 | case .success(let value): 593 | continuation.resume(returning: value) 594 | case .failure(let error): 595 | continuation.resume(throwing: error) 596 | } 597 | } 598 | } 599 | } 600 | 601 | public static func getBlockTransactionCountByNumber(blockNumber: Int) async throws -> Int { 602 | return try await withCheckedThrowingContinuation { continuation in 603 | getBlockTransactionCountByNumber(blockNumber: blockNumber) { result in 604 | switch result { 605 | case .success(let value): 606 | continuation.resume(returning: value) 607 | case .failure(let error): 608 | continuation.resume(throwing: error) 609 | } 610 | } 611 | } 612 | } 613 | 614 | public static func getCode(address: String, block: String = "latest") async throws -> String { 615 | return try await withCheckedThrowingContinuation { continuation in 616 | getCode(address: address, block: block) { result in 617 | switch result { 618 | case .success(let value): 619 | continuation.resume(returning: value) 620 | case .failure(let error): 621 | continuation.resume(throwing: error) 622 | } 623 | } 624 | } 625 | } 626 | 627 | public static func sendRawTransaction(account: Account, transaction: Transaction) async throws -> String { 628 | return try await withCheckedThrowingContinuation { continuation in 629 | sendRawTransaction(account: account, transaction: transaction) { result in 630 | switch result { 631 | case .success(let value): 632 | continuation.resume(returning: value) 633 | case .failure(let error): 634 | continuation.resume(throwing: error) 635 | } 636 | } 637 | } 638 | } 639 | 640 | public static func call(transaction: Transaction, block: String = "latest") async throws -> String { 641 | return try await withCheckedThrowingContinuation { continuation in 642 | call(transaction: transaction, block: block) { result in 643 | switch result { 644 | case .success(let value): 645 | continuation.resume(returning: value) 646 | case .failure(let error): 647 | continuation.resume(throwing: error) 648 | } 649 | } 650 | } 651 | } 652 | 653 | public static func estimateGas(for transaction: Transaction, block: String = "latest") async throws -> BigUInt { 654 | return try await withCheckedThrowingContinuation { continuation in 655 | estimateGas(for: transaction, block: block) { result in 656 | switch result { 657 | case .success(let value): 658 | continuation.resume(returning: value) 659 | case .failure(let error): 660 | continuation.resume(throwing: error) 661 | } 662 | } 663 | } 664 | } 665 | 666 | public static func getBlockByHash(hash: String) async throws -> Block { 667 | return try await withCheckedThrowingContinuation { continuation in 668 | getBlockByHash(hash: hash) { result in 669 | switch result { 670 | case .success(let value): 671 | continuation.resume(returning: value) 672 | case .failure(let error): 673 | continuation.resume(throwing: error) 674 | } 675 | } 676 | } 677 | } 678 | 679 | public static func getBlockByNumber(blockNumber: Int) async throws -> Block { 680 | return try await withCheckedThrowingContinuation { continuation in 681 | getBlockByNumber(blockNumber: blockNumber) { result in 682 | switch result { 683 | case .success(let value): 684 | continuation.resume(returning: value) 685 | case .failure(let error): 686 | continuation.resume(throwing: error) 687 | } 688 | } 689 | } 690 | } 691 | 692 | public static func getTransactionByHash(transactionHash: String) async throws -> Transaction { 693 | return try await withCheckedThrowingContinuation { continuation in 694 | getTransactionByHash(transactionHash: transactionHash) { result in 695 | switch result { 696 | case .success(let value): 697 | continuation.resume(returning: value) 698 | case .failure(let error): 699 | continuation.resume(throwing: error) 700 | } 701 | } 702 | } 703 | } 704 | 705 | public static func getTransactionByBlockHashAndIndex(blockHash: String, index: Int) async throws -> Transaction { 706 | return try await withCheckedThrowingContinuation { continuation in 707 | getTransactionByBlockHashAndIndex(blockHash: blockHash, index: index) { result in 708 | switch result { 709 | case .success(let value): 710 | continuation.resume(returning: value) 711 | case .failure(let error): 712 | continuation.resume(throwing: error) 713 | } 714 | } 715 | } 716 | } 717 | 718 | 719 | public static func getTransactionByBlockNumberAndIndex(blockNumber: Int, index: Int) async throws -> Transaction { 720 | return try await withCheckedThrowingContinuation { continuation in 721 | getTransactionByBlockNumberAndIndex(blockNumber: blockNumber, index: index) { result in 722 | switch result { 723 | case .success(let value): 724 | continuation.resume(returning: value) 725 | case .failure(let error): 726 | continuation.resume(throwing: error) 727 | } 728 | } 729 | } 730 | } 731 | 732 | public static func getTransactionReceipt(transactionHash: String) async throws -> Receipt { 733 | return try await withCheckedThrowingContinuation { continuation in 734 | getTransactionReceipt(transactionHash: transactionHash) { result in 735 | switch result { 736 | case .success(let value): 737 | continuation.resume(returning: value) 738 | case .failure(let error): 739 | continuation.resume(throwing: error) 740 | } 741 | } 742 | } 743 | } 744 | 745 | public static func getUncleByBlockHashAndIndex(blockHash: String, index: Int) async throws -> Block { 746 | return try await withCheckedThrowingContinuation { continuation in 747 | getUncleByBlockHashAndIndex(blockHash: blockHash, index: index) { result in 748 | switch result { 749 | case .success(let value): 750 | continuation.resume(returning: value) 751 | case .failure(let error): 752 | continuation.resume(throwing: error) 753 | } 754 | } 755 | } 756 | } 757 | 758 | public static func getUncleByBlockNumberAndIndex(blockNumber: Int, index: Int) async throws -> Block { 759 | return try await withCheckedThrowingContinuation { continuation in 760 | getUncleByBlockNumberAndIndex(blockNumber: blockNumber, index: index) { result in 761 | switch result { 762 | case .success(let value): 763 | continuation.resume(returning: value) 764 | case .failure(let error): 765 | continuation.resume(throwing: error) 766 | } 767 | } 768 | } 769 | } 770 | } 771 | -------------------------------------------------------------------------------- /Sources/Ethereum/Extensions/Data+Ext.swift: -------------------------------------------------------------------------------- 1 | // 2 | // File.swift 3 | // 4 | // 5 | // Created by Ertem Biyik on 05.06.2022. 6 | // 7 | 8 | import Foundation 9 | 10 | extension Data { 11 | 12 | var removeLeadingZeros: Data { 13 | var bytes = self.bytes 14 | while bytes.first == 0 { 15 | bytes.removeFirst() 16 | } 17 | return Data(bytes) 18 | } 19 | 20 | var removeTrailingZeros: Data { 21 | var bytes = self.bytes 22 | while bytes.last == 0 { 23 | bytes.removeLast() 24 | } 25 | return Data(bytes) 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /Sources/Ethereum/Extensions/String+Ext.swift: -------------------------------------------------------------------------------- 1 | // 2 | // File.swift 3 | // 4 | // 5 | // Created by Ertem Biyik on 05.06.2022. 6 | // 7 | 8 | import Foundation 9 | 10 | public extension String { 11 | 12 | func removeHexPrefix() -> String { 13 | 14 | if self.hasPrefix("0x") { 15 | let index = self.index(self.startIndex, offsetBy: 2) 16 | return String(self[index...]) 17 | } 18 | 19 | return self 20 | } 21 | 22 | func addHexPrefix() -> String { 23 | 24 | if self.hasPrefix("0x") { 25 | return self 26 | } 27 | 28 | return "0x" + self 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /Sources/Ethereum/Models/Account.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | import SwiftKeccak 3 | 4 | public struct Account { 5 | 6 | public let address: String 7 | public let publicKey: String 8 | public let privateKey: String 9 | 10 | init(privateKey: String) throws { 11 | 12 | self.privateKey = privateKey 13 | self.publicKey = try Utils.KeyUtils.getPublicKey(from: privateKey.removeHexPrefix()) 14 | self.address = try Utils.KeyUtils.getEthereumAddress(from: publicKey) 15 | } 16 | 17 | public func sign(_ value: T) throws -> Signature where T : RLPEncodable { 18 | 19 | let rlpData = try value.encodeRLP() 20 | 21 | let signedData = try Utils.KeyUtils.sign(data: rlpData, with: privateKey.removeHexPrefix()) 22 | 23 | return signedData 24 | } 25 | 26 | public func sign(transaction: Transaction) throws -> Transaction { 27 | 28 | var signature = try sign(transaction) 29 | 30 | signature.calculateV(with: transaction.chainID) 31 | 32 | let signedTransaction = try Transaction(transaction: transaction, signature: signature) 33 | 34 | return signedTransaction 35 | } 36 | } 37 | 38 | extension Account: Equatable {} 39 | -------------------------------------------------------------------------------- /Sources/Ethereum/Models/Block.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | public struct Block: Codable { 4 | public let difficulty: String 5 | public let extraData: String? 6 | public let gasLimit: String 7 | public let gasUsed: String 8 | public let hash: String 9 | public let logsBloom: String 10 | public let miner: String 11 | public let mixHash: String 12 | public let nonce: String 13 | public let number: String 14 | public let parentHash: String 15 | public let receiptsRoot: String 16 | public let sha3Uncles: String 17 | public let size: String 18 | public let stateRoot: String 19 | public let timestamp: String 20 | public let totalDifficulty: String? 21 | public let transactionsRoot: String 22 | public let transactions: [String]? 23 | public let uncles: [String]? 24 | } 25 | 26 | //304 27 | 28 | extension Block: Equatable {} 29 | -------------------------------------------------------------------------------- /Sources/Ethereum/Models/ConvertingError.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | public enum ConvertingError: Error { 4 | case errorConvertingFromHex 5 | } 6 | -------------------------------------------------------------------------------- /Sources/Ethereum/Models/JSONRPC/JSONRPCMethod.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | public enum JSONRPCMethod: String { 4 | 5 | case gasPrice = "eth_gasPrice" 6 | case blockNumber = "eth_blockNumber" 7 | case getBalance = "eth_getBalance" 8 | case getStorageAt = "eth_getStorageAt" 9 | case getTransactionCount = "eth_getTransactionCount" 10 | case getBlockTransactionCountByHash = "eth_getBlockTransactionCountByHash" 11 | case getBlockTransactionCountByNumber = "eth_getBlockTransactionCountByNumber" 12 | case getTransactionByHash = "eth_getTransactionByHash" 13 | case getCode = "eth_getCode" 14 | case version = "net_version" 15 | case listening = "net_listening" 16 | case peerCount = "net_peerCount" 17 | case clientVersion = "web3_clientVersion" 18 | case getBlockByHash = "eth_getBlockByHash" 19 | case getBlockByNumber = "eth_getBlockByNumber" 20 | case getUncleByBlockNumberAndIndex = "eth_getUncleByBlockNumberAndIndex" 21 | case getUncleByBlockHashAndIndex = "eth_getUncleByBlockHashAndIndex" 22 | case getTransactionByBlockHashAndIndex = "eth_getTransactionByBlockHashAndIndex" 23 | case getTransactionByBlockNumberAndIndex = "eth_getTransactionByBlockNumberAndIndex" 24 | case getTransactionReceipt = "eth_getTransactionReceipt" 25 | case sendRawTransaction = "eth_sendRawTransaction" 26 | case estimateGas = "eth_estimateGas" 27 | case call = "eth_call" 28 | } 29 | -------------------------------------------------------------------------------- /Sources/Ethereum/Models/JSONRPC/JSONRPCRequest.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | struct JSONRPCRequest: Encodable { 4 | 5 | let jsonrpc: String 6 | let method: String 7 | let params: T 8 | let id: Int 9 | 10 | init(jsonrpc: String, method: JSONRPCMethod, params: T, id: Int) { 11 | self.jsonrpc = jsonrpc 12 | self.method = method.rawValue 13 | self.params = params 14 | self.id = id 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /Sources/Ethereum/Models/JSONRPC/JSONRPCResponse.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | struct JSONRPCResponse: Decodable { 4 | 5 | let id: Int 6 | let jsonrpc: String 7 | let result: D 8 | } 9 | 10 | struct JSONRPCResponseError: Decodable { 11 | 12 | let id: Int 13 | let jsonrpc: String 14 | let error: JSONRPCErrorResult 15 | } 16 | 17 | public struct JSONRPCErrorResult: Decodable { 18 | 19 | public let code: Int 20 | public let message: String 21 | } 22 | -------------------------------------------------------------------------------- /Sources/Ethereum/Models/Log.swift: -------------------------------------------------------------------------------- 1 | // 2 | // File.swift 3 | // 4 | // 5 | // Created by Ertem Biyik on 29.03.2022. 6 | // 7 | 8 | import Foundation 9 | 10 | public struct Log: Codable { 11 | public let address: String 12 | public let topics: [String] 13 | public let data: String 14 | public let blockNumber: String 15 | public let transactionHash: String 16 | public let transactionIndex: String 17 | public let blockHash: String 18 | public let logIndex: String 19 | public let removed: Bool 20 | } 21 | 22 | extension Log: Equatable {} 23 | -------------------------------------------------------------------------------- /Sources/Ethereum/Models/Network.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | public enum Network: CaseIterable, Equatable { 4 | 5 | public static var allCases: [Network] = [.mainnet, .rinkeby, .ropsten, .kovan, .goerli, .custom(-1000)] 6 | 7 | case mainnet 8 | case rinkeby 9 | case ropsten 10 | case kovan 11 | case goerli 12 | case custom(Int) 13 | 14 | var chainID: Int { 15 | switch self { 16 | case .mainnet: 17 | return 1 18 | case .rinkeby: 19 | return 4 20 | case .ropsten: 21 | return 3 22 | case .kovan: 23 | return 42 24 | case .goerli: 25 | return 5 26 | case .custom(let value): 27 | return value 28 | } 29 | } 30 | } 31 | 32 | 33 | -------------------------------------------------------------------------------- /Sources/Ethereum/Models/Node.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | #if canImport(FoundationNetworking) 4 | import FoundationNetworking 5 | #endif 6 | 7 | public enum NodeErrors: Error { 8 | case errorRetrievingChainID 9 | case invalidURL 10 | } 11 | 12 | public struct Node { 13 | 14 | public let url: URL 15 | 16 | public var network: Network 17 | 18 | private var provider: Provider? 19 | 20 | public init(url: String) throws { 21 | 22 | guard let url = URL(string: url) else { 23 | throw NodeErrors.invalidURL 24 | } 25 | 26 | self.url = url 27 | self.network = .custom(0) 28 | 29 | configureProvider() 30 | 31 | var chainID: Int? 32 | var localError: Error? 33 | 34 | let group = DispatchGroup() 35 | 36 | group.enter() 37 | self.version { result in 38 | 39 | defer { group.leave() } 40 | 41 | switch result { 42 | case .success(let version): 43 | chainID = version 44 | case .failure(let error): 45 | localError = error 46 | } 47 | 48 | } 49 | 50 | // MARK: - Blocks thread, maybe switch to notify 51 | group.wait() 52 | 53 | guard localError == nil, let chainID = chainID else { 54 | throw localError ?? NodeErrors.errorRetrievingChainID 55 | } 56 | 57 | for net in Network.allCases { 58 | if net.chainID == chainID { 59 | self.network = net 60 | return 61 | } 62 | } 63 | 64 | self.network = .custom(chainID) 65 | 66 | } 67 | 68 | private init(url: URL, network: Network) { 69 | self.url = url 70 | self.network = network 71 | configureProvider() 72 | } 73 | 74 | private mutating func configureProvider() { 75 | self.provider = Provider(node: self) 76 | } 77 | 78 | 79 | // MARK: - Net 80 | public func version(completion: @escaping (Result) -> ()) { 81 | 82 | let params = [String]() 83 | 84 | provider?.sendRequest(method: .version, params: params, decodeTo: String.self) { result in 85 | 86 | switch result { 87 | case .success(let version): 88 | if let intVersion = Int(version) { 89 | completion(.success(intVersion)) 90 | } else { 91 | completion(.failure(ResponseError.errorDecodingJSONRPC)) 92 | } 93 | case .failure(let error): 94 | completion(.failure(error)) 95 | } 96 | } 97 | } 98 | 99 | public func listening(completion: @escaping (Result) -> ()) { 100 | 101 | let params = [String]() 102 | 103 | provider?.sendRequest(method: .listening, params: params, decodeTo: Bool.self) { result in 104 | 105 | switch result { 106 | case .success(let isListening): 107 | completion(.success(isListening)) 108 | case .failure(let error): 109 | completion(.failure(error)) 110 | } 111 | 112 | } 113 | } 114 | 115 | public func peerCount(completion: @escaping (Result) -> ()) { 116 | 117 | let params = [String]() 118 | 119 | provider?.sendRequest(method: .peerCount, params: params, decodeTo: String.self) { result in 120 | 121 | switch result { 122 | case .success(let hexPeerCount): 123 | if let peerCount = Int(hexPeerCount.removeHexPrefix(), radix: 16) { 124 | completion(.success(peerCount)) 125 | } else { 126 | completion(.failure(ConvertingError.errorConvertingFromHex)) 127 | } 128 | case .failure(let error): 129 | completion(.failure(error)) 130 | } 131 | 132 | } 133 | 134 | } 135 | 136 | // MARK: - Web3 137 | 138 | public func clientVersion(completion: @escaping (Result) -> ()) { 139 | 140 | let params = [String]() 141 | 142 | provider?.sendRequest(method: .clientVersion, params: params, decodeTo: String.self) { result in 143 | 144 | completion(result) 145 | 146 | } 147 | 148 | } 149 | } 150 | 151 | 152 | extension Node: Equatable { 153 | public static func == (lhs: Node, rhs: Node) -> Bool { 154 | return lhs.url == rhs.url && lhs.network == rhs.network 155 | } 156 | } 157 | 158 | extension Node { 159 | 160 | public func version() async throws -> Int { 161 | return try await withCheckedThrowingContinuation { continuation in 162 | version() { result in 163 | switch result { 164 | case .success(let value): 165 | continuation.resume(returning: value) 166 | case .failure(let error): 167 | continuation.resume(throwing: error) 168 | } 169 | } 170 | } 171 | } 172 | 173 | public func listening() async throws -> Bool { 174 | return try await withCheckedThrowingContinuation { continuation in 175 | listening() { result in 176 | switch result { 177 | case .success(let value): 178 | continuation.resume(returning: value) 179 | case .failure(let error): 180 | continuation.resume(throwing: error) 181 | } 182 | } 183 | } 184 | } 185 | 186 | public func peerCount() async throws -> Int { 187 | return try await withCheckedThrowingContinuation { continuation in 188 | peerCount() { result in 189 | switch result { 190 | case .success(let value): 191 | continuation.resume(returning: value) 192 | case .failure(let error): 193 | continuation.resume(throwing: error) 194 | } 195 | } 196 | } 197 | } 198 | 199 | public func clientVersion() async throws -> String { 200 | return try await withCheckedThrowingContinuation { continuation in 201 | clientVersion() { result in 202 | switch result { 203 | case .success(let value): 204 | continuation.resume(returning: value) 205 | case .failure(let error): 206 | continuation.resume(throwing: error) 207 | } 208 | } 209 | } 210 | } 211 | } 212 | 213 | -------------------------------------------------------------------------------- /Sources/Ethereum/Models/Receipt.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | public struct Receipt: Codable { 4 | public let blockHash: String 5 | public let blockNumber: String 6 | public let contractAddress: String? 7 | public let cumulativeGasUsed: String 8 | public let effectiveGasPrice: String 9 | public let from: String 10 | public let gasUsed: String 11 | public let logs: [Log] 12 | public let logsBloom: String 13 | public let status: String? 14 | public let to: String? 15 | public let root: String? 16 | public let transactionHash: String 17 | public let transactionIndex: String 18 | public let type: String 19 | } 20 | 21 | extension Receipt: Equatable {} 22 | -------------------------------------------------------------------------------- /Sources/Ethereum/Models/ResponseError.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | public enum ResponseError: Error { 4 | case errorEncodingJSONRPC 5 | case errorDecodingJSONRPC 6 | case nilResponse 7 | case noResult 8 | case errorSigningTransaction 9 | case ethereumError(JSONRPCErrorResult) 10 | case invalidAmount 11 | } 12 | 13 | -------------------------------------------------------------------------------- /Sources/Ethereum/Models/Signature.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | public struct Signature { 4 | 5 | public var v: Int 6 | public let r: Data 7 | public let s: Data 8 | 9 | public init(_ signedData: Data) { 10 | self.v = Int(signedData[64]) 11 | self.r = signedData.subdata(in: 0..<32).removeLeadingZeros 12 | self.s = signedData.subdata(in: 32..<64).removeLeadingZeros 13 | } 14 | 15 | public init(v: Int, r: Data, s: Data) { 16 | self.v = v 17 | self.r = r 18 | self.s = s 19 | } 20 | 21 | public mutating func calculateV(with chainID: Int?) { 22 | if v < 37 { 23 | v += (chainID ?? -1) * 2 + 35 24 | } 25 | } 26 | 27 | } 28 | 29 | extension Signature: Equatable {} 30 | -------------------------------------------------------------------------------- /Sources/Ethereum/Models/Transaction.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | import BigInt 3 | 4 | public struct Transaction: Codable { 5 | 6 | public let blockHash: String? 7 | public let blockNumber: String? 8 | public let from: String? 9 | public let gas: String? 10 | public let gasLimit: BigUInt? 11 | public let gasPrice: BigUInt? 12 | public var hash: String? 13 | public let input: Data 14 | public var nonce: Int? 15 | public let to: String 16 | public let value: BigUInt? 17 | public var chainID: Int? 18 | public let signature: Signature? 19 | 20 | public init( 21 | from: String? = nil, 22 | gasLimit: BigUInt? = nil, 23 | gasPrice: BigUInt? = nil, 24 | input: Data = Data(), 25 | nonce: Int? = nil, 26 | to: String, 27 | value: BigUInt? = nil) throws { 28 | self.blockHash = nil 29 | self.blockNumber = nil 30 | self.from = from 31 | self.gas = nil 32 | self.gasLimit = gasLimit 33 | self.gasPrice = gasPrice 34 | self.hash = nil 35 | self.input = input 36 | self.nonce = nonce 37 | self.to = to.removeHexPrefix().lowercased() 38 | self.value = value 39 | self.chainID = nil 40 | self.signature = nil 41 | 42 | } 43 | 44 | init(transaction: Transaction, signature: Signature) throws { 45 | self.blockHash = transaction.blockHash 46 | self.blockNumber = transaction.blockNumber 47 | self.from = transaction.from 48 | self.gas = transaction.gas 49 | self.gasLimit = transaction.gasLimit 50 | self.gasPrice = transaction.gasPrice 51 | self.hash = transaction.hash 52 | self.input = transaction.input 53 | self.nonce = transaction.nonce 54 | self.to = transaction.to 55 | self.value = transaction.value 56 | self.chainID = transaction.chainID 57 | self.signature = signature 58 | } 59 | 60 | private enum CodingKeys: String, CodingKey { 61 | case blockHash 62 | case blockNumber 63 | case from 64 | case gas 65 | case gasLimit 66 | case gasPrice 67 | case hash 68 | case input 69 | case nonce 70 | case to 71 | case value 72 | case chainId 73 | case v 74 | case r 75 | case s 76 | } 77 | 78 | public init(from decoder: Decoder) throws { 79 | 80 | let container = try decoder.container(keyedBy: CodingKeys.self) 81 | 82 | let decodeHexBigUInt = { (key: CodingKeys) throws -> BigUInt? in 83 | let decodedString = try container.decode(String.self, forKey: key) 84 | let decodedBigUInt = BigUInt(decodedString, radix: 16) 85 | return decodedBigUInt 86 | } 87 | 88 | let decodeHexInt = { (key: CodingKeys) throws -> Int? in 89 | let decodedString = try container.decode(String.self, forKey: key) 90 | return Int(decodedString, radix: 16) 91 | } 92 | 93 | let decodeData = { (key: CodingKeys) throws -> Data? in 94 | let decodedString = try container.decode(String.self, forKey: key) 95 | return try Data(decodedString.bytes) 96 | } 97 | 98 | self.blockHash = try? container.decode(String.self, forKey: .blockHash) 99 | self.blockNumber = try? container.decode(String.self, forKey: .blockNumber) 100 | self.from = (try? container.decode(String.self, forKey: .from)) ?? "" 101 | self.gas = try? decodeHexBigUInt(.gas)?.description 102 | self.gasLimit = try? decodeHexBigUInt(.gasLimit) 103 | self.gasPrice = try? decodeHexBigUInt(.gasPrice) 104 | self.hash = try? container.decode(String.self, forKey: .hash) 105 | self.input = (try? decodeData(.input)) ?? Data() 106 | self.nonce = try? decodeHexInt(.nonce) 107 | self.to = (try? container.decode(String.self, forKey: .to)) ?? "0x" 108 | self.value = try? decodeHexBigUInt(.value) 109 | self.chainID = (try? container.decode(Int.self, forKey: .chainId)) ?? 0 110 | 111 | if let v = try? decodeHexInt(.v), let r = try? decodeData(.r), let s = try? decodeData(.s) { 112 | self.signature = Signature(v: v, r: r, s: s) 113 | } else { 114 | self.signature = nil 115 | } 116 | } 117 | 118 | public func encode(to encoder: Encoder) throws { 119 | 120 | var container = encoder.container(keyedBy: CodingKeys.self) 121 | 122 | let encodeBigUInt = { (value: BigUInt?, key: CodingKeys) throws -> Void in 123 | if let value = value { 124 | let encodedValue = String(value, radix: 16).addHexPrefix() 125 | try container.encode(encodedValue, forKey: key) 126 | } 127 | 128 | } 129 | 130 | let encodeOptionalString = { (value: String?, key: CodingKeys) throws -> Void in 131 | if let value = value { 132 | try container.encode(value.addHexPrefix(), forKey: key) 133 | } 134 | } 135 | 136 | let encodeData = { (value: Data, key: CodingKeys) throws -> Void in 137 | 138 | if value == Data() { 139 | try container.encode("", forKey: key) 140 | } else { 141 | let encodedValue = String(bytes: value).addHexPrefix() 142 | try container.encode(encodedValue, forKey: key) 143 | } 144 | } 145 | 146 | try? encodeOptionalString(to, .to) 147 | try? encodeOptionalString(from, .from) 148 | try? encodeData(input, .input) 149 | try? encodeBigUInt(value, .value) 150 | try? encodeBigUInt(gasPrice, .gasPrice) 151 | try? encodeBigUInt(gasLimit, .gas) 152 | } 153 | } 154 | 155 | extension Transaction: RLPEncodable { 156 | 157 | public func encodeRLP() throws -> Data { 158 | if let signature = signature { 159 | 160 | let array: [RLPEncodable?] = [nonce, 161 | gasPrice, 162 | gasLimit, 163 | to, 164 | value, 165 | input, 166 | signature.v, 167 | signature.r, 168 | signature.s] 169 | 170 | let arrayRLP = array.compactMap { $0 } 171 | 172 | return try RLPEncoder.encode(arrayRLP) 173 | 174 | } else { 175 | 176 | let array: [RLPEncodable?] = [nonce, 177 | gasPrice, 178 | gasLimit, 179 | to, 180 | value, 181 | input, 182 | chainID, 183 | 0, 184 | 0] 185 | 186 | let arrayRLP: [RLPEncodable] = array.compactMap { $0 } 187 | 188 | return try RLPEncoder.encode(arrayRLP) 189 | } 190 | } 191 | } 192 | 193 | extension Transaction: Equatable {} 194 | -------------------------------------------------------------------------------- /Sources/Ethereum/Provider/Provider.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | #if canImport(FoundationNetworking) 4 | import FoundationNetworking 5 | #endif 6 | 7 | public final class Provider { 8 | 9 | public let node: Node 10 | 11 | private let session: URLSession 12 | 13 | private let encoder = JSONEncoder() 14 | 15 | private let decoder = JSONDecoder() 16 | 17 | private let queue = DispatchQueue(label: "com.swift-ethereum.provider", attributes: .concurrent) 18 | 19 | public init(node: Node, sessionConfiguration: URLSessionConfiguration) { 20 | self.node = node 21 | self.session = URLSession(configuration: sessionConfiguration, delegate: nil, delegateQueue: nil) 22 | } 23 | 24 | public convenience init(node: Node) { 25 | self.init(node: node, sessionConfiguration: URLSession.shared.configuration) 26 | } 27 | 28 | deinit { 29 | self.session.finishTasksAndInvalidate() 30 | } 31 | 32 | /* 33 | Method that is called from Service to send a request 34 | */ 35 | func sendRequest(method: JSONRPCMethod, params: E, decodeTo: D.Type, completion: @escaping (Result) -> Void) { 36 | 37 | queue.async { [weak self] in 38 | 39 | guard let self = self else { 40 | completion(.failure(ProviderError.providerIsNil)) 41 | return 42 | } 43 | 44 | var request = URLRequest(url: self.node.url) 45 | request.httpMethod = "POST" 46 | request.addValue("application/json", forHTTPHeaderField: "Content-Type") 47 | request.addValue("application/json", forHTTPHeaderField: "Accept") 48 | 49 | let id = Int.random(in: 1...1000) 50 | 51 | let jsonRPC = JSONRPCRequest(jsonrpc: "2.0", method: method, params: params, id: id) 52 | 53 | guard let jsonRPCData = try? JSONEncoder().encode(jsonRPC) else { 54 | completion(.failure(ResponseError.errorEncodingJSONRPC)) 55 | return 56 | } 57 | 58 | request.httpBody = jsonRPCData 59 | 60 | let task = self.session.dataTask(with: request) { data, response, error in 61 | 62 | guard let data = data, error == nil else { 63 | completion(.failure(ResponseError.nilResponse)) 64 | return 65 | } 66 | 67 | if let ethereumError = try? JSONDecoder().decode(JSONRPCResponseError.self, from: data) { 68 | completion(.failure(ResponseError.ethereumError(ethereumError.error))) 69 | return 70 | } 71 | 72 | guard let jsonRPCResponse = try? JSONDecoder().decode(JSONRPCResponse.self, from: data) else { 73 | completion(.failure(ResponseError.errorDecodingJSONRPC)) 74 | return 75 | } 76 | 77 | completion(.success(jsonRPCResponse.result)) 78 | } 79 | task.resume() 80 | } 81 | } 82 | } 83 | 84 | 85 | extension Provider { 86 | func sendRequest(method: JSONRPCMethod, params: E, decodeTo: D.Type) async throws -> D { 87 | return try await withCheckedThrowingContinuation { continuation in 88 | sendRequest(method: method, params: params, decodeTo: decodeTo) { result in 89 | switch result { 90 | case .success(let value): 91 | continuation.resume(returning: value) 92 | case .failure(let error): 93 | continuation.resume(throwing: error) 94 | } 95 | } 96 | } 97 | } 98 | } 99 | 100 | -------------------------------------------------------------------------------- /Sources/Ethereum/Provider/ProviderError.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | public enum ProviderError: Error { 4 | case providerIsNil 5 | } 6 | -------------------------------------------------------------------------------- /Sources/Ethereum/RLP/RLPEncodable.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | import BigInt 3 | 4 | public protocol RLPEncodable { 5 | func encodeRLP() throws -> Data 6 | } 7 | 8 | enum RLPEncoderError: Error { 9 | case unvalidString 10 | case negativeInteger 11 | } 12 | 13 | extension String: RLPEncodable { 14 | 15 | public func encodeRLP() throws -> Data { 16 | 17 | if let bytesArray = try? self.bytes { 18 | let hexData = Data(bytesArray) 19 | return try hexData.encodeRLP() 20 | } 21 | 22 | guard let data = self.data(using: .utf8) else { 23 | throw RLPEncoderError.unvalidString 24 | } 25 | 26 | return try data.encodeRLP() 27 | } 28 | 29 | func decodeRLP() throws -> Data { 30 | return Data() 31 | } 32 | 33 | } 34 | 35 | extension Int: RLPEncodable { 36 | 37 | public func encodeRLP() throws -> Data { 38 | 39 | guard self >= 0 else { 40 | throw RLPEncoderError.negativeInteger 41 | } 42 | 43 | return try BigInt(self).encodeRLP() 44 | } 45 | 46 | func decodeRLP() throws -> Data { 47 | return Data() 48 | } 49 | 50 | 51 | } 52 | 53 | extension BigInt: RLPEncodable { 54 | 55 | public func encodeRLP() throws -> Data { 56 | 57 | guard self >= 0 else { 58 | throw RLPEncoderError.negativeInteger 59 | } 60 | 61 | return try BigUInt(self).encodeRLP() 62 | } 63 | 64 | func decodeRLP() throws -> Data { 65 | return Data() 66 | } 67 | 68 | 69 | } 70 | 71 | extension BigUInt: RLPEncodable { 72 | 73 | public func encodeRLP() throws -> Data { 74 | 75 | let data = self.serialize() 76 | 77 | let lastIndex = data.count - 1 78 | 79 | let firstIndex = data.firstIndex(where: { $0 != 0x00 } ) ?? lastIndex 80 | 81 | if lastIndex == -1 { 82 | return Data( [0x80]) 83 | } 84 | 85 | let subdata = data.subdata(in: firstIndex.. Data { 96 | return Data() 97 | } 98 | } 99 | 100 | extension Data: RLPEncodable { 101 | 102 | public func encodeRLP() throws -> Data { 103 | 104 | if self.count == 1 && self[0] <= 0x7f { 105 | return self // single byte, no header 106 | } 107 | 108 | var encoded = encodeHeader(size: UInt64(self.count), smallTag: 0x80, largeTag: 0xb7) 109 | 110 | encoded.append(self) 111 | 112 | return encoded 113 | } 114 | 115 | func decodeRLP() throws -> Data { 116 | return Data() 117 | } 118 | } 119 | 120 | extension Array: RLPEncodable where Element: RLPEncodable { 121 | 122 | public func encodeRLP() throws -> Data { 123 | 124 | var encodedData = Data() 125 | 126 | for element in self { 127 | do { 128 | let encoded = try element.encodeRLP() 129 | encodedData.append(encoded) 130 | } catch { 131 | throw error 132 | } 133 | } 134 | 135 | var encoded = encodeHeader(size: UInt64(encodedData.count), smallTag: 0xc0, largeTag: 0xf7) 136 | 137 | encoded.append(encodedData) 138 | 139 | return encoded 140 | } 141 | 142 | func decodeRLP() throws -> Data { 143 | return Data() 144 | } 145 | } 146 | 147 | extension RLPEncodable { 148 | 149 | func encodeHeader(size: UInt64, smallTag: UInt8, largeTag: UInt8) -> Data { 150 | 151 | if size < 56 { 152 | return Data([smallTag + UInt8(size)]) 153 | } 154 | 155 | let sizeData = bigEndianBinary(size) 156 | 157 | var encoded = Data() 158 | 159 | encoded.append(largeTag + UInt8(sizeData.count)) 160 | encoded.append(contentsOf: sizeData) 161 | 162 | return encoded 163 | } 164 | 165 | func bigEndianBinary(_ i: UInt64) -> Data { 166 | 167 | var value = i 168 | 169 | var bytes = withUnsafeBytes(of: &value) { Array($0) } 170 | 171 | for (index, byte) in bytes.enumerated().reversed() { 172 | if index != 0 && byte == 0x00 { 173 | bytes.remove(at: index) 174 | } else { 175 | break 176 | } 177 | } 178 | 179 | return Data(bytes.reversed()) 180 | } 181 | } 182 | -------------------------------------------------------------------------------- /Sources/Ethereum/RLP/RLPEncoder.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | import BigInt 3 | 4 | enum RLPEncoder { 5 | 6 | static func encode(_ array: [RLPEncodable]) throws -> Data { 7 | 8 | var encodedData = Data() 9 | 10 | for element in array { 11 | 12 | do { 13 | let encoded = try element.encodeRLP() 14 | encodedData.append(encoded) 15 | } catch { 16 | throw error 17 | } 18 | } 19 | 20 | var encoded = encodeHeader(size: UInt64(encodedData.count), smallTag: 0xc0, largeTag: 0xf7) 21 | 22 | encoded.append(encodedData) 23 | 24 | return encoded 25 | 26 | } 27 | 28 | private static func encodeHeader(size: UInt64, smallTag: UInt8, largeTag: UInt8) -> Data { 29 | 30 | if size < 56 { 31 | return Data([smallTag + UInt8(size)]) 32 | } 33 | 34 | let sizeData = bigEndianBinary(size) 35 | 36 | var encoded = Data() 37 | 38 | encoded.append(largeTag + UInt8(sizeData.count)) 39 | encoded.append(contentsOf: sizeData) 40 | 41 | return encoded 42 | } 43 | 44 | private static func bigEndianBinary(_ i: UInt64) -> Data { 45 | 46 | var value = i 47 | 48 | var bytes = withUnsafeBytes(of: &value) { Array($0) } 49 | 50 | for (index, byte) in bytes.enumerated().reversed() { 51 | if index != 0 && byte == 0x00 { 52 | bytes.remove(at: index) 53 | } else { 54 | break 55 | } 56 | } 57 | 58 | return Data(bytes.reversed()) 59 | } 60 | 61 | } 62 | -------------------------------------------------------------------------------- /Sources/Ethereum/RLP/RLPError.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | public enum RLPError: Error { 4 | case invalidData 5 | } 6 | -------------------------------------------------------------------------------- /Sources/Ethereum/SmartContract/ABI/ABIDecoder.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | import BigInt 3 | 4 | public enum ABIDecoder { 5 | 6 | public enum ABIDecoderError: Error { 7 | case errorDecodingInt 8 | case errorDecodingString 9 | case noSequenceElementTypeProvidedForArray 10 | } 11 | 12 | static public func decode(_ value: String, to type: SmartContractValueType) throws -> Any { 13 | 14 | let bytes = try value.removeHexPrefix().bytes 15 | 16 | let data = Data(bytes) 17 | 18 | return try decode(data, to: [type]) 19 | } 20 | 21 | static public func decode(_ data: Data, to type: SmartContractValueType) throws -> Any { 22 | 23 | return try decode(data, to: [type]) 24 | } 25 | 26 | static public func decode(_ value: String, to types: [SmartContractValueType]) throws -> Any { 27 | 28 | let bytes = try value.removeHexPrefix().bytes 29 | 30 | let data = Data(bytes) 31 | 32 | return try decode(data, to: types) 33 | } 34 | 35 | static public func decode(_ data: Data, to types: [SmartContractValueType]) throws -> Any { 36 | 37 | var values = [Any]() 38 | 39 | for word in 0.. 1 ? values : values[0] 61 | } 62 | 63 | // MARK: - Only for static data parts or for pointed dynamic 64 | static private func decode(_ value: String, type: SmartContractValueType) throws -> Any { 65 | 66 | let bytes = try value.bytes 67 | 68 | let data = Data(bytes) 69 | 70 | return try decode(data, type: type) 71 | 72 | } 73 | 74 | static private func decode(_ data: Data, type: SmartContractValueType) throws -> Any { 75 | 76 | switch type { 77 | case .address: 78 | return try decodeAddress(from: data) 79 | case .uint(let bits): 80 | switch bits { 81 | case 8: 82 | return try decodeUInt8(from: data) 83 | case 16: 84 | return try decodeUInt16(from: data) 85 | case 32: 86 | return try decodeUInt32(from: data) 87 | case 64: 88 | return try decodeUInt64(from: data) 89 | default: 90 | return try decodeUInt256(from: data) 91 | } 92 | 93 | case .int(let bits): 94 | switch bits { 95 | case 8: 96 | return try decodeInt8(from: data) 97 | case 16: 98 | return try decodeInt16(from: data) 99 | case 32: 100 | return try decodeInt32(from: data) 101 | case 64: 102 | return try decodeInt64(from: data) 103 | default: 104 | return try decodeInt256(from: data) 105 | } 106 | case .bool: 107 | return try decodeBool(from: data) 108 | case .string: 109 | return try decodeString(from: data) 110 | case .array(let type, let length): 111 | return try decodeArray(of: type, length: length, from: data) 112 | case .bytes(let length): 113 | return try decodeBytes(from: data, length: length) 114 | case .fixed(_, _): 115 | return Data() 116 | case .ufixed(_, _): 117 | return Data() 118 | case .tuple(_): 119 | return [String]() 120 | } 121 | 122 | } 123 | 124 | static private func decodeAddress(from data: Data) throws -> String { 125 | 126 | let addressData = data.subdata(in: 12..<32) 127 | 128 | let ethereumAddress = "0x" + String(bytes: addressData) 129 | 130 | return ethereumAddress 131 | } 132 | 133 | static private func decodeUInt8(from data: Data) throws -> UInt8 { 134 | 135 | let uintData = data.subdata(in: 0..<32).removeTrailingZeros 136 | 137 | let stringValue = String(bytes: uintData) 138 | 139 | guard let uint = UInt8(stringValue, radix: 16) else { 140 | throw ABIDecoderError.errorDecodingInt 141 | } 142 | 143 | return uint 144 | } 145 | 146 | static private func decodeUInt16(from data: Data) throws -> UInt16 { 147 | 148 | let uintData = data.subdata(in: 0..<32).removeTrailingZeros 149 | 150 | let stringValue = String(bytes: uintData) 151 | 152 | guard let uint = UInt16(stringValue, radix: 16) else { 153 | throw ABIDecoderError.errorDecodingInt 154 | } 155 | 156 | return uint 157 | } 158 | 159 | 160 | static private func decodeUInt32(from data: Data) throws -> UInt32 { 161 | 162 | let uintData = data.subdata(in: 0..<32).removeTrailingZeros 163 | 164 | let stringValue = String(bytes: uintData) 165 | 166 | guard let uint = UInt32(stringValue, radix: 16) else { 167 | throw ABIDecoderError.errorDecodingInt 168 | } 169 | 170 | return uint 171 | } 172 | 173 | static private func decodeUInt64(from data: Data) throws -> UInt64 { 174 | 175 | let uintData = data.subdata(in: 0..<32).removeTrailingZeros 176 | 177 | let stringValue = String(bytes: uintData) 178 | 179 | guard let uint = UInt64(stringValue, radix: 16) else { 180 | throw ABIDecoderError.errorDecodingInt 181 | } 182 | 183 | return uint 184 | } 185 | 186 | static private func decodeUInt256(from data: Data) throws -> BigUInt { 187 | 188 | let uintData = data.subdata(in: 0..<32).removeTrailingZeros 189 | 190 | let stringValue = String(bytes: uintData) 191 | 192 | guard let uint = BigUInt(stringValue, radix: 16) else { 193 | throw ABIDecoderError.errorDecodingInt 194 | } 195 | 196 | return uint 197 | } 198 | 199 | static private func decodeUInt(from data: Data) throws -> UInt { 200 | 201 | let uintData = data.subdata(in: 0..<32).removeTrailingZeros 202 | 203 | let stringValue = String(bytes: uintData) 204 | 205 | guard let uint = UInt(stringValue, radix: 16) else { 206 | throw ABIDecoderError.errorDecodingInt 207 | } 208 | 209 | return uint 210 | } 211 | 212 | static private func decodeInt8(from data: Data) throws -> Int8 { 213 | 214 | let intData = data.subdata(in: 0..<32).removeTrailingZeros 215 | 216 | let stringValue = String(bytes: intData) 217 | 218 | guard let int = Int8(stringValue, radix: 16) else { 219 | throw ABIDecoderError.errorDecodingInt 220 | } 221 | 222 | return int 223 | } 224 | 225 | static private func decodeInt16(from data: Data) throws -> Int16 { 226 | 227 | let intData = data.subdata(in: 0..<32).removeTrailingZeros 228 | 229 | let stringValue = String(bytes: intData) 230 | 231 | guard let int = Int16(stringValue, radix: 16) else { 232 | throw ABIDecoderError.errorDecodingInt 233 | } 234 | 235 | return int 236 | } 237 | 238 | static private func decodeInt32(from data: Data) throws -> Int32 { 239 | 240 | let intData = data.subdata(in: 0..<32).removeTrailingZeros 241 | 242 | let stringValue = String(bytes: intData) 243 | 244 | guard let int = Int32(stringValue, radix: 16) else { 245 | throw ABIDecoderError.errorDecodingInt 246 | } 247 | 248 | return int 249 | } 250 | 251 | static private func decodeInt64(from data: Data) throws -> Int64 { 252 | 253 | let intData = data.subdata(in: 0..<32).removeTrailingZeros 254 | 255 | let stringValue = String(bytes: intData) 256 | 257 | guard let int = Int64(stringValue, radix: 16) else { 258 | throw ABIDecoderError.errorDecodingInt 259 | } 260 | 261 | return int 262 | } 263 | 264 | static private func decodeInt256(from data: Data) throws -> BigInt { 265 | 266 | let intData = data.subdata(in: 0..<32).removeTrailingZeros 267 | 268 | let stringValue = String(bytes: intData) 269 | 270 | guard let int = BigInt(stringValue, radix: 16) else { 271 | throw ABIDecoderError.errorDecodingInt 272 | } 273 | 274 | return int 275 | } 276 | 277 | static private func decodeInt(from data: Data) throws -> Int { 278 | 279 | let intData = data.subdata(in: 0..<32).removeTrailingZeros 280 | 281 | let stringValue = String(bytes: intData) 282 | 283 | guard let int = Int(stringValue, radix: 16) else { 284 | throw ABIDecoderError.errorDecodingInt 285 | } 286 | 287 | return int 288 | } 289 | 290 | static private func decodeBool(from data: Data) throws -> Bool { 291 | 292 | let boolData = data.subdata(in: 0..<32).removeTrailingZeros 293 | 294 | return boolData.last == 1 ? true : false 295 | } 296 | 297 | static private func decodeString(from data: Data) throws -> String { 298 | 299 | let utf8Data = try decodeBytes(from: data, length: nil) 300 | 301 | guard let string = String(bytes: utf8Data, encoding: .utf8) else { 302 | throw ABIDecoderError.errorDecodingString 303 | } 304 | 305 | return string 306 | } 307 | 308 | static private func decodeArray(of type: SmartContractValueType, length: UInt?, from data: Data) throws -> [Any] { 309 | 310 | // the resulting values 311 | var values = [Any]() 312 | 313 | var lengthOfArray = 0 314 | var arrayData = data 315 | 316 | // calculate the length of an array from the first word or if the length provided use it 317 | if let length = length { 318 | lengthOfArray = Int(length) 319 | } else { 320 | lengthOfArray = try decodeInt(from: data) 321 | arrayData = data.subdata(in: 32.. Data { 349 | 350 | var decodedData = Data() 351 | var encodedData = data 352 | 353 | var countOfWords = Double() 354 | var countOfBytes = Int() 355 | 356 | if let length = length { 357 | countOfBytes = Int(length) 358 | } else { 359 | countOfBytes = try decodeInt(from: data) 360 | encodedData = data.subdata(in: 32.. Data 7 | } 8 | 9 | public enum ABIEncoderError: Error { 10 | case invalidMethodName 11 | case invalidStringParam 12 | } 13 | 14 | extension ABIEthereumAddress: ABIEncodable { 15 | 16 | public func encodeABI(isDynamic: Bool, sequenceElementType: SmartContractValueType?) throws -> Data { 17 | 18 | let bytes = try self.address.removeHexPrefix().lowercased().bytes 19 | 20 | let data = Data(bytes) 21 | 22 | let paddedData = Data(repeating: 0x00, count: 12) + data 23 | 24 | return paddedData 25 | } 26 | } 27 | 28 | extension BigUInt: ABIEncodable { 29 | 30 | public func encodeABI(isDynamic: Bool, sequenceElementType: SmartContractValueType?) throws -> Data { 31 | 32 | let data = self.serialize() 33 | 34 | let paddedData = Data(repeating: 0x00, count: 32 - data.count) + data 35 | 36 | return paddedData 37 | } 38 | } 39 | 40 | extension BigInt: ABIEncodable { 41 | 42 | public func encodeABI(isDynamic: Bool, sequenceElementType: SmartContractValueType?) throws -> Data { 43 | 44 | let data = self.serialize() 45 | 46 | var paddedData = Data() 47 | 48 | if self >= 0 { 49 | paddedData = Data(repeating: 0x00, count: 32 - data.count) + data 50 | } else { 51 | paddedData = Data(repeating: 0xff, count: 32 - data.count) + data 52 | } 53 | 54 | return paddedData 55 | } 56 | } 57 | 58 | extension FixedWidthInteger where Self: SignedInteger { 59 | 60 | public func encodeABI(isDynamic: Bool, sequenceElementType: SmartContractValueType?) throws -> Data { 61 | 62 | let bigInt = BigInt(self) 63 | 64 | return try bigInt.encodeABI(isDynamic: isDynamic, sequenceElementType: nil) 65 | } 66 | } 67 | 68 | extension FixedWidthInteger where Self: UnsignedInteger { 69 | 70 | public func encodeABI(isDynamic: Bool, sequenceElementType: SmartContractValueType?) throws -> Data { 71 | 72 | let bigUInt = BigUInt(self) 73 | 74 | return try bigUInt.encodeABI(isDynamic: isDynamic, sequenceElementType: nil) 75 | } 76 | } 77 | 78 | extension Int: ABIEncodable { } 79 | extension Int8: ABIEncodable { } 80 | extension Int16: ABIEncodable { } 81 | extension Int32: ABIEncodable { } 82 | extension Int64: ABIEncodable { } 83 | 84 | extension UInt: ABIEncodable { } 85 | extension UInt8: ABIEncodable { } 86 | extension UInt16: ABIEncodable { } 87 | extension UInt32: ABIEncodable { } 88 | extension UInt64: ABIEncodable { } 89 | 90 | 91 | extension Bool: ABIEncodable { 92 | 93 | public func encodeABI(isDynamic: Bool, sequenceElementType: SmartContractValueType?) throws -> Data { 94 | 95 | let uintValue = self ? BigUInt(1) : BigUInt(0) 96 | 97 | let data = uintValue.serialize() 98 | 99 | let paddedData = Data(repeating: 0x00, count: 32 - data.count) + data 100 | 101 | return paddedData 102 | } 103 | } 104 | 105 | extension String: ABIEncodable { 106 | 107 | public func encodeABI(isDynamic: Bool, sequenceElementType: SmartContractValueType?) throws -> Data { 108 | 109 | // MARK: - Use count for characters or for bits in utf8 encoded data? 110 | let bigUIntCount = BigUInt(self.count) 111 | 112 | let lengthData = try bigUIntCount.encodeABI(isDynamic: false, sequenceElementType: nil) 113 | 114 | guard let utfData = self.data(using: .utf8) else { throw ABIEncoderError.invalidStringParam } 115 | 116 | let paddedData = utfData + Data(repeating: 0x00, count: 32 - utfData.count) 117 | 118 | return lengthData + paddedData 119 | } 120 | } 121 | 122 | extension Data: ABIEncodable { 123 | 124 | public func encodeABI(isDynamic: Bool, sequenceElementType: SmartContractValueType?) throws -> Data { 125 | 126 | let bigUIntCount = BigUInt(self.count) 127 | 128 | let lengthData = try bigUIntCount.encodeABI(isDynamic: false, sequenceElementType: nil) 129 | 130 | let paddedData = self + Data(repeating: 0x00, count: 32 - self.count) 131 | 132 | return isDynamic ? lengthData + paddedData : paddedData 133 | } 134 | } 135 | 136 | extension Array: ABIEncodable where Element: ABIEncodable { 137 | 138 | public func encodeABI(isDynamic: Bool, sequenceElementType: SmartContractValueType?) throws -> Data { 139 | 140 | let bigUIntCount = BigUInt(self.count) 141 | 142 | let lengthData = try bigUIntCount.encodeABI(isDynamic: false, sequenceElementType: nil) 143 | 144 | var staticSignature = Data() 145 | 146 | var dynamicSignature = Data() 147 | 148 | for value in self { 149 | if sequenceElementType?.isDynamic == true { 150 | 151 | //calculate an offset for dynamic type and append it to static signature 152 | let bigUIntCount = BigUInt(self.count * 32 + dynamicSignature.count) 153 | let offset = try bigUIntCount.encodeABI(isDynamic: false, sequenceElementType: nil) 154 | staticSignature.append(offset) 155 | 156 | // calculate the encoded value of a given param and append it to dynamic signature 157 | let encodedParam = try value.encodeABI(isDynamic: true, sequenceElementType: sequenceElementType?.sequenceElementType) 158 | dynamicSignature.append(encodedParam) 159 | } else { 160 | let encodedParam = try value.encodeABI(isDynamic: false, sequenceElementType: sequenceElementType?.sequenceElementType) 161 | staticSignature.append(encodedParam) 162 | } 163 | } 164 | 165 | return isDynamic ? lengthData + staticSignature + dynamicSignature : staticSignature + dynamicSignature 166 | } 167 | 168 | } 169 | -------------------------------------------------------------------------------- /Sources/Ethereum/SmartContract/ABI/ABIEncoder.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | import SwiftKeccak 3 | import BigInt 4 | 5 | public enum ABIEncoder { 6 | 7 | public static func encode(method: SmartContractMethod) throws -> Data { 8 | 9 | let fullMethodName = method.name + "(" + method.params.map { $0.type.stringValue }.joined(separator: ",") + ")" 10 | 11 | guard let methodData = fullMethodName.data(using: .utf8) else { 12 | throw ABIEncoderError.invalidMethodName 13 | } 14 | 15 | let kecckakBytes = methodData.keccak() 16 | 17 | let methodSignature = kecckakBytes.subdata(in: 0..<4) 18 | 19 | var staticParamsSignature = Data() 20 | 21 | var dynamicParamsSignature = Data() 22 | 23 | for param in method.params { 24 | 25 | switch param.type.isDynamic { 26 | 27 | case true: 28 | 29 | // calculate an offset for dynamic type and append it to static signature 30 | let bigUIntCount = BigUInt(method.params.count * 32 + dynamicParamsSignature.count) 31 | let offset = try bigUIntCount.encodeABI(isDynamic: false, sequenceElementType: nil) 32 | staticParamsSignature.append(offset) 33 | 34 | // calculate the encoded value of a given param and append it to dynamic signature 35 | let encodedParam = try param.value.encodeABI(isDynamic: true, sequenceElementType: param.type.sequenceElementType) 36 | dynamicParamsSignature.append(encodedParam) 37 | 38 | case false: 39 | let encodedParam = try param.value.encodeABI(isDynamic: false, sequenceElementType: param.type.sequenceElementType) 40 | staticParamsSignature.append(encodedParam) 41 | } 42 | 43 | } 44 | 45 | return methodSignature + staticParamsSignature + dynamicParamsSignature 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /Sources/Ethereum/SmartContract/ABI/ABIError.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | public enum ABIError: Error { 4 | 5 | case errorEncodingToABI 6 | case errorDecodingFromABI 7 | } 8 | -------------------------------------------------------------------------------- /Sources/Ethereum/SmartContract/ABI/ABIEthereumAddress.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | struct ABIEthereumAddress { 4 | let address: String 5 | 6 | init(_ address: String) { 7 | self.address = address.lowercased() 8 | } 9 | } 10 | 11 | extension ABIEthereumAddress: Equatable {} 12 | -------------------------------------------------------------------------------- /Sources/Ethereum/SmartContract/ERC20.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | import BigInt 3 | 4 | public enum ERC20TransactionFactory { 5 | 6 | public static func generateTransferTransaction(value: BigUInt, to: String, gasLimit: BigUInt?, gasPrice: BigUInt?, contractAddress: String) throws -> Transaction { 7 | 8 | let params = [SmartContractParam(type: .address, value: ABIEthereumAddress(to)), 9 | SmartContractParam(type: .uint(), value: value)] 10 | 11 | let method = SmartContractMethod(name: "transfer", params: params) 12 | 13 | guard let data = method.abiData else { 14 | throw ABIError.errorEncodingToABI 15 | } 16 | 17 | return try Transaction(gasLimit: gasLimit, gasPrice: gasPrice, input: data, to: contractAddress, value: BigUInt(0)) 18 | } 19 | 20 | public static func generateBalanceTransaction(address: String, contractAddress: String) throws -> Transaction { 21 | 22 | let params = [SmartContractParam(type: .address, value: ABIEthereumAddress(address))] 23 | 24 | let method = SmartContractMethod(name: "balanceOf", params: params) 25 | 26 | guard let data = method.abiData else { 27 | throw ABIError.errorEncodingToABI 28 | } 29 | 30 | return try Transaction(input: data, to: contractAddress) 31 | 32 | } 33 | 34 | public static func generateDecimalsTransaction(contractAddress: String) throws -> Transaction { 35 | 36 | let method = SmartContractMethod(name: "decimals", params: []) 37 | 38 | guard let data = method.abiData else { 39 | throw ABIError.errorEncodingToABI 40 | } 41 | 42 | return try Transaction(input: data, to: contractAddress) 43 | } 44 | 45 | public static func generateSymbolTransaction(contractAddress: String) throws -> Transaction { 46 | 47 | let method = SmartContractMethod(name: "symbol", params: []) 48 | 49 | guard let data = method.abiData else { 50 | throw ABIError.errorEncodingToABI 51 | } 52 | 53 | return try Transaction(input: data, to: contractAddress) 54 | } 55 | 56 | public static func generateNameTransaction(contractAddress: String) throws -> Transaction { 57 | 58 | let method = SmartContractMethod(name: "name", params: []) 59 | 60 | guard let data = method.abiData else { 61 | throw ABIError.errorEncodingToABI 62 | } 63 | 64 | return try Transaction(input: data, to: contractAddress) 65 | } 66 | 67 | public static func generateTotalSupplyTransaction(contractAddress: String) throws -> Transaction { 68 | 69 | let method = SmartContractMethod(name: "totalSupply", params: []) 70 | 71 | guard let data = method.abiData else { 72 | throw ABIError.errorEncodingToABI 73 | } 74 | 75 | return try Transaction(input: data, to: contractAddress) 76 | } 77 | } 78 | 79 | //func deploy(with account: Account, binary: Data, gasLimit: BigUInt, gasPrice: BigUInt, completion: @escaping (String?, Error?) -> ()) { 80 | // 81 | // var transaction: Transaction 82 | // 83 | // do { 84 | // transaction = try Transaction(gasLimit: gasLimit, gasPrice: gasPrice, input: binary, to: "0x", value: BigUInt(0)) 85 | // } catch { 86 | // completion(nil, error) 87 | // return 88 | // } 89 | // 90 | // EthereumService.sendRawTransaction(account: account, transaction: transaction) { hash, error in 91 | // 92 | // completion(hash, error) 93 | // } 94 | // 95 | //} 96 | // 97 | // 98 | 99 | //func approve(spender: Account, value: BigUInt, completion: @escaping (BigUInt?, Error?) -> ()) { 100 | // 101 | // let params = [SmartContractParam(type: .address, value: EthereumAddress(spender.address)), 102 | // SmartContractParam(type: .uint(), value: value)] 103 | // 104 | // let method = SmartContractMethod(name: "approve", params: params) 105 | // 106 | // guard let data = method.abiData else { 107 | // completion(nil, ABIError.errorEncodingToABI) 108 | // return 109 | // } 110 | // 111 | // var transaction: Transaction 112 | // 113 | // do { 114 | // transaction = try Transaction(input: data, to: self.address) 115 | // } catch { 116 | // completion(nil, error) 117 | // return 118 | // } 119 | // 120 | // EthereumService.call(transaction: transaction) { hexValue, error in 121 | // 122 | // guard let hexValue = hexValue, error == nil else { 123 | // completion(nil, error) 124 | // return 125 | // } 126 | // 127 | // let value = hexValue.removeHexPrefix() 128 | // 129 | // guard let bigUIntValue = try? ABIDecoder.decode(value, to: .uint()) as? BigUInt else { 130 | // completion(nil, ABIError.errorDecodingFromABI) 131 | // return 132 | // } 133 | // 134 | // completion(bigUIntValue, nil) 135 | // } 136 | //} 137 | -------------------------------------------------------------------------------- /Sources/Ethereum/SmartContract/ERC721.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | import BigInt 3 | 4 | public enum ERC721TransactionFactory { 5 | 6 | public static func generateTransferTransaction(tokenId: BigUInt, to address: String, gasPrice: BigUInt?, gasLimit: BigUInt?, contractAddress: String) throws -> Transaction { 7 | 8 | let params = [SmartContractParam(type: .address, value: ABIEthereumAddress(address)), 9 | SmartContractParam(type: .uint(), value: tokenId)] 10 | 11 | let method = SmartContractMethod(name: "transfer", params: params) 12 | 13 | guard let data = method.abiData else { 14 | throw ABIError.errorEncodingToABI 15 | } 16 | 17 | return try Transaction(gasLimit: gasLimit, gasPrice: gasPrice, input: data, to: contractAddress, value: BigUInt(0)) 18 | } 19 | 20 | public static func generateBalanceTransaction(address: String, contractAddress: String) throws -> Transaction { 21 | 22 | let params = [SmartContractParam(type: .address, value: ABIEthereumAddress(address))] 23 | 24 | let method = SmartContractMethod(name: "balanceOf", params: params) 25 | 26 | guard let data = method.abiData else { 27 | throw ABIError.errorEncodingToABI 28 | } 29 | 30 | return try Transaction(input: data, to: contractAddress) 31 | } 32 | 33 | public static func generateOwnerOfTransaction(tokenId: BigUInt, contractAddress: String) throws -> Transaction { 34 | 35 | let params = [SmartContractParam(type: .uint(), value: tokenId)] 36 | 37 | let method = SmartContractMethod(name: "ownerOf", params: params) 38 | 39 | guard let data = method.abiData else { 40 | throw ABIError.errorEncodingToABI 41 | } 42 | 43 | return try Transaction(input: data, to: contractAddress) 44 | } 45 | 46 | public static func generateNameTransaction(contractAddress: String) throws -> Transaction { 47 | 48 | let method = SmartContractMethod(name: "name", params: []) 49 | 50 | guard let data = method.abiData else { 51 | throw ABIError.errorEncodingToABI 52 | } 53 | 54 | return try Transaction(input: data, to: contractAddress) 55 | } 56 | 57 | public static func generateSymbolTransaction(contractAddress: String) throws -> Transaction { 58 | 59 | let method = SmartContractMethod(name: "symbol", params: []) 60 | 61 | guard let data = method.abiData else { 62 | throw ABIError.errorEncodingToABI 63 | } 64 | 65 | return try Transaction(input: data, to: contractAddress) 66 | } 67 | 68 | public static func generateTokenURITransaction(tokenId: BigUInt, contractAddress: String) throws -> Transaction { 69 | 70 | let params = [SmartContractParam(type: .uint(), value: tokenId)] 71 | 72 | let method = SmartContractMethod(name: "tokenURI", params: params) 73 | 74 | guard let data = method.abiData else { 75 | throw ABIError.errorEncodingToABI 76 | } 77 | 78 | return try Transaction(input: data, to: contractAddress) 79 | } 80 | 81 | public static func generateTotalSupplyTransaction(contractAddress: String) throws -> Transaction { 82 | 83 | let method = SmartContractMethod(name: "totalSupply", params: []) 84 | 85 | guard let data = method.abiData else { 86 | throw ABIError.errorEncodingToABI 87 | } 88 | 89 | return try Transaction(input: data, to: contractAddress) 90 | } 91 | 92 | public static func generateTokenByIndexTransaction(index: BigUInt, contractAddress: String) throws -> Transaction { 93 | 94 | let params = [SmartContractParam(type: .uint(), value: index)] 95 | 96 | let method = SmartContractMethod(name: "tokenByIndex", params: params) 97 | 98 | guard let data = method.abiData else { 99 | throw ABIError.errorEncodingToABI 100 | } 101 | 102 | return try Transaction(input: data, to: contractAddress) 103 | } 104 | 105 | public static func generateTokenOfOwnerByIndexTransaction(ownerAddress: String, index: BigUInt, contractAddress: String) throws -> Transaction { 106 | 107 | let params = [SmartContractParam(type: .address, value: ABIEthereumAddress(ownerAddress)), 108 | SmartContractParam(type: .uint(), value: index)] 109 | 110 | let method = SmartContractMethod(name: "tokenOfOwnerByIndex", params: params) 111 | 112 | guard let data = method.abiData else { 113 | throw ABIError.errorEncodingToABI 114 | } 115 | 116 | return try Transaction(input: data, to: contractAddress) 117 | } 118 | 119 | // func mint(to address: String, tokenId: String, tokenURI: String, with account: Account, completion: @escaping (String?, Error?) -> ()) { 120 | // 121 | // let params = [SmartContractParam(type: .address, value: EthereumAddress(address)), 122 | // SmartContractParam(type: .string, value: tokenURI)] 123 | // 124 | // let method = SmartContractMethod(name: "_mint", params: params) 125 | // 126 | // guard let data = method.abiData else { 127 | // completion(nil, ABIError.errorEncodingToABI) 128 | // return 129 | // } 130 | // 131 | // var transaction: Transaction 132 | // 133 | // do { 134 | // transaction = try Transaction(gasLimit: gasLimit, gasPrice: gasPrice, input: data, to: self.address, value: BigUInt(0)) 135 | // } catch { 136 | // completion(nil, error) 137 | // return 138 | // } 139 | // 140 | // EthereumService.sendRawTransaction(account: account, transaction: transaction) { hash, error in 141 | // completion(hash, error) 142 | // } 143 | // } 144 | 145 | } 146 | -------------------------------------------------------------------------------- /Sources/Ethereum/SmartContract/SmartContract.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | import BigInt 3 | 4 | protocol SmartContractProtocol { 5 | 6 | init(abi: String, address: String) 7 | 8 | // MARK: - write an extension with default realisation 9 | func method(name: String, params: T) -> Transaction 10 | 11 | var allMethods: [String] { get } 12 | 13 | var allEvents: [String] { get } 14 | 15 | } 16 | 17 | extension SmartContractProtocol { 18 | // base realisation of smart contracts (call method) 19 | } 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /Sources/Ethereum/SmartContract/SmartContractMethod.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | public struct SmartContractMethod { 4 | 5 | let name: String 6 | let params: [SmartContractParam] 7 | 8 | var abiData: Data? { 9 | return try? ABIEncoder.encode(method: self) 10 | } 11 | 12 | public init(name: String, params: [SmartContractParam]) { 13 | self.name = name 14 | self.params = params 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /Sources/Ethereum/SmartContract/SmartContractParam.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | public struct SmartContractParam { 4 | 5 | let name: String 6 | let type: SmartContractValueType 7 | let value: ABIEncodable 8 | 9 | public init(name: String = "", type: SmartContractValueType, value: ABIEncodable) { 10 | self.name = name 11 | self.type = type 12 | self.value = value 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /Sources/Ethereum/SmartContract/SmartContractValueType.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | import BigInt 3 | 4 | public indirect enum SmartContractValueType { 5 | 6 | case address 7 | 8 | case uint(_ bits: UInt16 = 256) 9 | 10 | case int(_ bits: UInt16 = 256) 11 | 12 | case bool 13 | 14 | case string 15 | 16 | case array(type: SmartContractValueType, length: UInt? = nil) 17 | 18 | case bytes(_ length: UInt? = nil) 19 | 20 | case fixed(_ bits: UInt16 = 128, _ length: UInt8 = 18) 21 | 22 | case ufixed(_ bits: UInt16 = 128, _ length: UInt8 = 18) 23 | 24 | case tuple([SmartContractValueType]) 25 | 26 | var stringValue: String { 27 | switch self { 28 | 29 | case .address: 30 | return "address" 31 | 32 | case .uint(let bits): 33 | return "uint\(bits)" 34 | 35 | case .int(let bits): 36 | return "int\(bits)" 37 | 38 | case .bool: 39 | return "bool" 40 | 41 | case .bytes(length: let length): 42 | if let length = length { 43 | return "bytes\(length)" 44 | } 45 | return "bytes" 46 | 47 | case .string: 48 | return "string" 49 | 50 | case .array(type: let type, length: let length): 51 | if let length = length { 52 | return type.stringValue + "[\(length)]" 53 | } 54 | return type.stringValue + "[]" 55 | 56 | case .fixed(_: let bits, _: let length): 57 | return "fixed\(bits)x\(length)" 58 | 59 | case .ufixed(_: let bits, _: let length): 60 | return "ufixed\(bits)x\(length)" 61 | 62 | case .tuple(let types): 63 | let typesString = types.map { $0.stringValue }.joined(separator: ",") 64 | return "(\(typesString))" 65 | } 66 | } 67 | 68 | var isDynamic: Bool { 69 | switch self { 70 | case .string: 71 | return true 72 | case .array(_, let length): 73 | return length == nil 74 | case .bytes(let length): 75 | return length == nil 76 | case .tuple(let types): 77 | return types.count > 1 || types.filter { $0.isDynamic }.count > 0 78 | default: 79 | return false 80 | } 81 | } 82 | 83 | var sequenceElementType: SmartContractValueType? { 84 | switch self { 85 | case .array(let type, _): 86 | return type 87 | default: 88 | return nil 89 | } 90 | } 91 | } 92 | -------------------------------------------------------------------------------- /Sources/Ethereum/Storage/AES.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | import CommonCrypto 3 | 4 | public struct AES { 5 | 6 | public var initialVector: Data { 7 | let randomBytes = Data(0...16).map { _ in UInt32.random(in: 0...UInt32.max) } 8 | 9 | return Data(bytes: randomBytes, count: 16) 10 | } 11 | 12 | public func encrypt(_ data: Data, password: String, iv: Data) throws -> Data { 13 | 14 | let keyData = password.keccak() 15 | 16 | guard keyData.count == kCCKeySizeAES256 else { 17 | throw AESError.invalidPasswordLength 18 | } 19 | 20 | return try crypt(data: data, option: CCOperation(kCCEncrypt), key: keyData, iv: iv) 21 | } 22 | 23 | public func encrypt(_ privateKey: String, password: String, iv: Data) throws -> Data { 24 | 25 | let keyData = password.keccak() 26 | 27 | guard keyData.count == kCCKeySizeAES256 else { 28 | throw AESError.invalidPasswordLength 29 | } 30 | 31 | let bytes = try privateKey.bytes 32 | 33 | let data = Data(bytes) 34 | 35 | return try crypt(data: data, option: CCOperation(kCCEncrypt), key: keyData, iv: iv) 36 | } 37 | 38 | public func decrypt(_ data: Data, password: String, iv: Data) throws -> String { 39 | 40 | let keyData = password.keccak() 41 | 42 | guard keyData.count == kCCKeySizeAES256 else { 43 | throw AESError.invalidPasswordLength 44 | } 45 | 46 | let decryptedData = try crypt(data: data, option: CCOperation(kCCDecrypt), key: keyData, iv: iv) 47 | 48 | let value = String(bytes: decryptedData) 49 | 50 | return value 51 | } 52 | 53 | func crypt(data: Data, option: CCOperation, key: Data, iv: Data) throws -> Data { 54 | 55 | let cryptLength = data.count + kCCBlockSizeAES128 56 | var cryptData = Data(count: cryptLength) 57 | 58 | let keyLength = key.count 59 | let options = CCOptions(kCCOptionPKCS7Padding) 60 | 61 | var bytesLength = Int(0) 62 | 63 | let status = cryptData.withUnsafeMutableBytes { cryptBytes in 64 | data.withUnsafeBytes { dataBytes in 65 | iv.withUnsafeBytes { ivBytes in 66 | key.withUnsafeBytes { keyBytes in 67 | CCCrypt(option, 68 | CCAlgorithm(kCCAlgorithmAES), 69 | options, 70 | keyBytes.baseAddress, 71 | keyLength, 72 | ivBytes.baseAddress, 73 | dataBytes.baseAddress, 74 | data.count, 75 | cryptBytes.baseAddress, 76 | cryptLength, 77 | &bytesLength) 78 | } 79 | } 80 | } 81 | } 82 | 83 | guard UInt32(status) == UInt32(kCCSuccess) else { 84 | throw AESError.errorCrypting 85 | } 86 | 87 | cryptData.removeSubrange(bytesLength.. String { 18 | return "" 19 | } 20 | 21 | public func removePrivateKey(for address: String) throws { 22 | 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /Sources/Ethereum/Storage/Storage.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | public enum Storage { 4 | 5 | case userDefaults 6 | case keychain 7 | case icloud 8 | } 9 | 10 | /** 11 | Ethereum: StorageProtocol that describes Storage object 12 | */ 13 | public protocol StorageProtocol { 14 | 15 | func storePrivateKey(_ privateKey: String) throws 16 | func getPrivateKey(for address: String) throws -> String 17 | func removePrivateKey(for address: String) throws 18 | } 19 | 20 | public enum StorageError: Error { 21 | case noValueForKey(String) 22 | } 23 | -------------------------------------------------------------------------------- /Sources/Ethereum/Storage/StorageFile.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | public struct StorageFile: Codable { 4 | public let keyData: Data 5 | public let iv: Data 6 | } 7 | -------------------------------------------------------------------------------- /Sources/Ethereum/Storage/UserDefaultsStorage.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | public struct UserDefaultsStorage: StorageProtocol { 4 | 5 | private let aes = AES() 6 | 7 | private let password: String 8 | 9 | public init(password: String) { 10 | self.password = password 11 | } 12 | 13 | public func storePrivateKey(_ privateKey: String) throws { 14 | 15 | let publicKey = try Utils.KeyUtils.getPublicKey(from: privateKey) 16 | 17 | let address = try Utils.KeyUtils.getEthereumAddress(from: publicKey) 18 | 19 | let iv = aes.initialVector 20 | 21 | let aesEncryptedPrivateKey = try aes.encrypt(privateKey, password: password, iv: iv) 22 | 23 | let file = StorageFile(keyData: aesEncryptedPrivateKey, iv: iv) 24 | 25 | let encodedFile = try JSONEncoder().encode(file) 26 | 27 | UserDefaults.standard.set(encodedFile, forKey: address) 28 | } 29 | 30 | public func getPrivateKey(for address: String) throws -> String { 31 | 32 | guard let file = UserDefaults.standard.data(forKey: address) else { 33 | throw StorageError.noValueForKey(address) 34 | } 35 | 36 | let decodedFile = try JSONDecoder().decode(StorageFile.self, from: file) 37 | 38 | let aesEncryptedPrivateKey = decodedFile.keyData 39 | 40 | let iv = decodedFile.iv 41 | 42 | let decryptedPrivateKey = try aes.decrypt(aesEncryptedPrivateKey, password: password, iv: iv) 43 | 44 | return decryptedPrivateKey 45 | } 46 | 47 | public func removePrivateKey(for address: String) throws { 48 | UserDefaults.standard.set(nil, forKey: address) 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /Sources/Ethereum/Storage/iCloudStorage.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | struct iCloudStorage: StorageProtocol { 4 | 5 | private let aes = AES() 6 | 7 | private let password: String 8 | 9 | public init(password: String) { 10 | self.password = password 11 | } 12 | 13 | public func storePrivateKey(_ privateKey: String) throws { 14 | 15 | 16 | } 17 | 18 | public func getPrivateKey(for address: String) throws -> String { 19 | return "" 20 | } 21 | 22 | public func removePrivateKey(for address: String) throws { 23 | 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /Sources/Ethereum/Uitils.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | import BigInt 3 | import SwiftKeccak 4 | import secp256k1 5 | 6 | public enum Utils { } 7 | 8 | public extension Utils { 9 | 10 | enum KeyUtils { 11 | 12 | public static func getPublicKey(from privateKey: String) throws -> String { 13 | 14 | let privateKeyBytes = try privateKey.lowercased().removeHexPrefix().bytes 15 | 16 | let secp256k1PrivateKey = try secp256k1.Signing.PrivateKey(rawRepresentation: privateKeyBytes, format: .uncompressed) 17 | 18 | let publicKey = secp256k1PrivateKey.publicKey.rawRepresentation.subdata(in: 1..<65) 19 | 20 | return String(bytes: publicKey) 21 | } 22 | 23 | public static func getEthereumAddress(from publicKey: String) throws -> String { 24 | 25 | let publicKeyBytes = try publicKey.lowercased().bytes 26 | 27 | let publicKeyData = Data(bytes: publicKeyBytes, count: 64) 28 | 29 | let hash = publicKeyData.keccak() 30 | 31 | let address = hash.subdata(in: 12.. Signature { 39 | 40 | let privateKeyBytes = try privateKey.lowercased().removeHexPrefix().bytes 41 | 42 | let secp256k1PrivateKey = try secp256k1.Signing.PrivateKey(rawRepresentation: privateKeyBytes, format: .uncompressed) 43 | 44 | // MARK: - Make use of simplified syntax 45 | 46 | // let keccakData = data.keccak() 47 | // 48 | // let digests = SHA256.convert(keccakData.bytes) 49 | // 50 | // let signature = try secp256k1PrivateKey.ecdsa.signature(for: digests) 51 | // 52 | 53 | guard let context = secp256k1_context_create(UInt32(SECP256K1_CONTEXT_SIGN | SECP256K1_CONTEXT_VERIFY)) else { 54 | print("Failed to sign message: invalid context.") 55 | throw ResponseError.errorSigningTransaction 56 | } 57 | 58 | defer { 59 | secp256k1_context_destroy(context) 60 | } 61 | 62 | let keccakData = data.keccak() 63 | 64 | let keccakDataPointer = (keccakData as NSData).bytes.assumingMemoryBound(to: UInt8.self) 65 | 66 | let privateKeyPointer = (secp256k1PrivateKey.rawRepresentation as NSData).bytes.assumingMemoryBound(to: UInt8.self) 67 | 68 | let signaturePointer = UnsafeMutablePointer.allocate(capacity: 1) 69 | 70 | defer { 71 | signaturePointer.deallocate() 72 | } 73 | 74 | guard secp256k1_ecdsa_sign_recoverable(context, signaturePointer, keccakDataPointer, privateKeyPointer, nil, nil) == 1 else { 75 | print("Failed to sign message: recoverable ECDSA signature creation failed.") 76 | throw ResponseError.errorSigningTransaction 77 | } 78 | 79 | let outputDataPointer = UnsafeMutablePointer.allocate(capacity: 64) 80 | 81 | defer { 82 | outputDataPointer.deallocate() 83 | } 84 | 85 | var recoverableID: Int32 = 0 86 | 87 | secp256k1_ecdsa_recoverable_signature_serialize_compact(context, outputDataPointer, &recoverableID, signaturePointer) 88 | 89 | let outputWithRecoverableIDPointer = UnsafeMutablePointer.allocate(capacity: 65) 90 | 91 | defer { 92 | outputWithRecoverableIDPointer.deallocate() 93 | } 94 | 95 | outputWithRecoverableIDPointer.assign(from: outputDataPointer, count: 64) 96 | outputWithRecoverableIDPointer.advanced(by: 64).pointee = UInt8(recoverableID) 97 | 98 | let signedData = Data(bytes: outputWithRecoverableIDPointer, count: 65) 99 | 100 | let signature = Signature(signedData) 101 | 102 | return signature 103 | } 104 | } 105 | } 106 | 107 | public extension Utils { 108 | 109 | enum Formatter { 110 | 111 | static var currencyFormatter: NumberFormatter { 112 | let currencyFormatter = NumberFormatter() 113 | currencyFormatter.usesGroupingSeparator = true 114 | currencyFormatter.numberStyle = .currency 115 | currencyFormatter.currencySymbol = "Ξ" 116 | currencyFormatter.locale = Locale.current 117 | return currencyFormatter 118 | } 119 | 120 | } 121 | } 122 | 123 | public extension Utils { 124 | 125 | enum Converter { 126 | 127 | public enum EthereumUnits { 128 | case eth 129 | case wei 130 | case gwei 131 | } 132 | 133 | public static func convert(value: String, from: EthereumUnits, to: EthereumUnits) -> String { 134 | 135 | switch from { 136 | case .eth: 137 | switch to { 138 | case .eth: 139 | return value 140 | case .wei: 141 | return transformDown(value: value, base: 18) 142 | case .gwei: 143 | return transformDown(value: value, base: 9) 144 | } 145 | case .wei: 146 | switch to { 147 | case .eth: 148 | return transformUp(value: value, base: 18) 149 | case .wei: 150 | return value 151 | case .gwei: 152 | return transformUp(value: value, base: 9) 153 | } 154 | 155 | case .gwei: 156 | switch to { 157 | case .eth: 158 | return transformUp(value: value, base: 9) 159 | case .wei: 160 | return transformDown(value: value, base: 9) 161 | case .gwei: 162 | return value 163 | } 164 | 165 | } 166 | } 167 | 168 | private static func transformUp(value: String, base: Int) -> String { 169 | 170 | var result = "0." 171 | 172 | if value.count < base { 173 | let zerosLeft = String(repeating: "0", count: base - value.count) 174 | result.append(contentsOf: zerosLeft + value) 175 | } else { 176 | let integerPartIndex = value.index(value.startIndex, offsetBy: value.count - base) 177 | let integerPart = value[value.startIndex.. String { 196 | 197 | // check if value is integer 198 | if value.range(of: "[0-9]*[.]", options: [.regularExpression, .anchored]) == nil { 199 | return value + String(repeating: "0", count: base) 200 | } else if value.first == "0" { // if value is not integer and < 1 201 | let fractionalPartIndex = value.index(value.startIndex, offsetBy: 2) 202 | let fractionalPart = value[fractionalPartIndex...] 203 | let additionalZeros = String(repeating: "0", count: base - fractionalPart.count) 204 | return fractionalPart + additionalZeros 205 | } else { // if value is not integer and > 1 206 | let indexOfDot = value.firstIndex(of: ".")! 207 | let integerPart = value[value.startIndex..