diff --git a/Documentation/README.md b/Documentation/README.md index eee39ce96..d2a71412e 100644 --- a/Documentation/README.md +++ b/Documentation/README.md @@ -74,11 +74,13 @@ Here are quick references for essential features: - [x] [EIP-155](https://github.com/ethereum/EIPs/blob/develop/EIPS/eip-155.md) (Replay attacks protection) *enforced!* + - [x] [EIP-165](https://github.com/ethereum/EIPs/blob/develop/EIPS/eip-165.md) (Standard Interface Detection, also known as ERC-165) + - [x] [EIP-681](https://github.com/ethereum/EIPs/blob/develop/EIPS/eip-681.md) (A standard way of representing various transactions, especially payment requests in Ethers and ERC-20 tokens as URLs) - [x] [EIP-721](https://github.com/ethereum/EIPs/blob/develop/EIPS/eip-721.md) (A standard interface for non-fungible tokens, also known as deeds - ERC-721) - - [x] [EIP-165](https://github.com/ethereum/EIPs/blob/develop/EIPS/eip-165.md) (Standard Interface Detection, also known as ERC-165) + - [x] [EIP-721x](https://github.com/loomnetwork/erc721x) (An extension of ERC721 that adds support for multi-fungible tokens and batch transfers, while being fully backward-compatible, also known as ERC-721x) - [x] [EIP-777](https://github.com/ethereum/EIPs/blob/develop/EIPS/eip-777.md) (New Advanced Token Standard, also known as ERC-777) @@ -86,23 +88,27 @@ Here are quick references for essential features: - [x] [EIP-888](https://github.com/ethereum/EIPs/issues/888) (MultiDimensional Token Standard, also known as ERC-888) + - [x] [EIP-1155](https://github.com/ethereum/EIPs/blob/develop/EIPS/eip-1155.md) (Multi Token Standard, also known as ERC-1155) + + - [x] [EIP-1376](https://github.com/ethereum/EIPs/issues/1376) (Service-Friendly Token, also known as ERC-1376) + - [x] [EIP-1400](https://github.com/ethereum/EIPs/issues/1411) (Security Token Standard, also known as ERC-1400) - [x] [EIP-1410](https://github.com/ethereum/EIPs/issues/1410) (Partially Fungible Token Standard, also known as ERC-1410) + - [x] [EIP-1559](https://github.com/ethereum/EIPs/blob/master/EIPS/eip-1559.md) (Fee market change for ETH 1.0 chain) + - [x] [EIP-1594](https://github.com/ethereum/EIPs/issues/1594) (Core Security Token Standard, also known as ERC-1594) + - [x] [EIP-1633](https://github.com/ethereum/EIPs/issues/1634) (Re-Fungible Token, also known as ERC-1633) + - [x] [EIP-1643](https://github.com/ethereum/EIPs/issues/1643) (Document Management Standard, also known as ERC-1643) - [x] [EIP-1644](https://github.com/ethereum/EIPs/issues/1644) (Controller Token Operation Standard, also known as ERC-1644) - - [x] [EIP-1633](https://github.com/ethereum/EIPs/issues/1634) (Re-Fungible Token, also known as ERC-1633) - - - [x] [EIP-721x](https://github.com/loomnetwork/erc721x) (An extension of ERC721 that adds support for multi-fungible tokens and batch transfers, while being fully backward-compatible, also known as ERC-721x) - - - [x] [EIP-1155](https://github.com/ethereum/EIPs/blob/develop/EIPS/eip-1155.md) (Multi Token Standard, also known as ERC-1155) + - [x] [EIP-2718](https://github.com/ethereum/EIPs/blob/master/EIPS/eip-2718.md) (Typed Transaction Envelope) - - [x] [EIP-1376](https://github.com/ethereum/EIPs/issues/1376) (Service-Friendly Token, also known as ERC-1376) + - [x] [EIP-2930](https://github.com/ethereum/EIPs/blob/master/EIPS/eip-2930.md) (Optional access lists) - [x] [ST-20](https://github.com/PolymathNetwork/polymath-core) - ST-20 token is an Ethereum-based token implemented on top of the ERC-20 protocol that adds the ability for tokens to control transfers based on specific rules diff --git a/Documentation/Usage.md b/Documentation/Usage.md index ee5449323..b030ec8f0 100755 --- a/Documentation/Usage.md +++ b/Documentation/Usage.md @@ -32,6 +32,7 @@ - [Send ERC-20 Token](#send-erc-20-token) - [Write Transaction and call smart contract method](#write-transaction-and-call-smart-contract-method) - [Read Transaction to call smart contract method](#read-transaction-to-call-smart-contract-method) + - [Other Transaction Types (EIP-1559)](#other-transaction-types) - [Send Transaction](#send-transaction) - [Write](#write) - [Read](#read) @@ -355,6 +356,40 @@ let result = try! transaction.send(password: password) let result = try! transaction.call() ``` +##### Other Transaction Types + +By default a `legacy` transaction will be created which is compatible across all chains, regardless of which fork. +To create one of the new transaction types introduced with the `london` fork you will need to set some additonal parameters +in the `TransactionOptions` object. Note you should only try to send one of tehse new types of transactions if you are on a chain +that supports them. + +To send an EIP-2930 style transacton with an access list you need to set the transaction type, and the access list, +in addition what is shown in the examples above. +```swift +let accessList: [AccessListEntry] = [ + AccessListEntry( + address: EthereumAddress("0xde0b295669a9fd93d5f28d9ec85e40f4cb697bae"), + storageKeys: [BigUInt(3), BigUInt(7)] + ), + AccessListEntry( + address: EthereumAddress("0xbb9bc244d798123fde783fcc1c72d3bb8c189413"), + storageKeys: [] + ) +] + +options.type = .eip2930 +options.accessList = accessList +``` + +To send an EIP-1559 style transaction you set the transaction type, and the new gas parameters `maxFeePerGas` and `maxPriorityFeePerGas` +(you may also send an AccessList with an EIP-1559 transaction) When sending an EIP-1559 transaction, the older `gasPrice` parameter is ignored. +```swift +options.type = .eip1559 +options.maxFeePerGas = .manual(...) // the maximum price per unit of gas, inclusive of baseFee and tip +options.maxPriorityFeePerGas = .manual(...) // the tip to be paid to the miner, per unit of gas +``` +Note there is a new `Oracle` object available that can be used to assist with estimating the new gas fees + ### Chain state #### Get Block number diff --git a/README.md b/README.md index a2f1a5cab..fabf5765e 100755 --- a/README.md +++ b/README.md @@ -57,7 +57,7 @@ - [x] ✅**Literally following the standards** (BIP, EIP, etc): - [x] **[BIP32](https://github.com/bitcoin/bips/blob/master/bip-0032.mediawiki) (HD Wallets), [BIP39](https://github.com/bitcoin/bips/blob/master/bip-0039.mediawiki) (Seed phrases), [BIP44](https://github.com/bitcoin/bips/blob/master/bip-0044.mediawiki) (Key generation prefixes)** -- [x] **[EIP-20](https://github.com/ethereum/EIPs/blob/master/EIPS/eip-20.md)** (Standart interface for tokens - ERC-20), **[EIP-67](https://github.com/ethereum/EIPs/issues/67)** (Standard URI scheme), **[EIP-155](https://github.com/ethereum/EIPs/blob/master/EIPS/eip-155.md)** (Replay attacks protection) +- [x] **[EIP-20](https://github.com/ethereum/EIPs/blob/master/EIPS/eip-20.md)** (Standart interface for tokens - ERC-20), **[EIP-67](https://github.com/ethereum/EIPs/issues/67)** (Standard URI scheme), **[EIP-155](https://github.com/ethereum/EIPs/blob/master/EIPS/eip-155.md)** (Replay attacks protection), **[EIP-2718](https://github.com/ethereum/EIPs/blob/master/EIPS/eip-2718.md)** (Typed Transaction Envelope), **[EIP-1559](https://github.com/ethereum/EIPs/blob/master/EIPS/eip-1559.md)** (Gas Fee market change) - [x] **And many others** *(For details about this EIP's look at [Documentation page](https://github.com/skywinder/web3swift/blob/master/Documentation/))*: EIP-681, EIP-721, EIP-165, EIP-777, EIP-820, EIP-888, EIP-1400, EIP-1410, EIP-1594, EIP-1643, EIP-1644, EIP-1633, EIP-721, EIP-1155, EIP-1376, ST-20 - [x] 🗜 **Batched requests** in concurrent mode diff --git a/Sources/web3swift/Transaction/EIP1559Envelope.swift b/Sources/web3swift/Transaction/EIP1559Envelope.swift index 1ad6039b1..5944dac54 100644 --- a/Sources/web3swift/Transaction/EIP1559Envelope.swift +++ b/Sources/web3swift/Transaction/EIP1559Envelope.swift @@ -41,12 +41,6 @@ public struct EIP1559Envelope: EIP2718Envelope { public var maxFeePerGas: BigUInt public var accessList: [AccessListEntry] // from EIP-2930 - /// EIP-1159 trnsactions do not have a gasPrice parameter - /// However, it appears that some nodes report a gasPrice, even for EIP-1159 transactions - /// thus for a temporary workaround we capture and store gasPrice if initialized from a JSON transaction - /// decided form a node. This is currently needed for Oracle to work - private var gasPrice: BigUInt = 0 - // for CustomStringConvertible public var description: String { var toReturn = "" @@ -76,9 +70,6 @@ public struct EIP1559Envelope: EIP2718Envelope { value: value, data: data, gasLimit: gasLimit, - // MARK: workaround for gasPrice coming from nodes for EIP-1159 - this allows Oracle to work for now - gasPrice: gasPrice, - maxFeePerGas: maxFeePerGas, maxPriorityFeePerGas: maxPriorityFeePerGas, accessList: accessList @@ -94,8 +85,6 @@ public struct EIP1559Envelope: EIP2718Envelope { maxFeePerGas = val.maxFeePerGas ?? maxFeePerGas maxPriorityFeePerGas = val.maxPriorityFeePerGas ?? maxPriorityFeePerGas accessList = val.accessList ?? accessList - // MARK: workaround for gasPrice coming from nodes for EIP-1159 - this allows Oracle to work for now - gasPrice = val.gasPrice ?? gasPrice } } @@ -117,8 +106,6 @@ extension EIP1559Envelope { case v case r case s - // MARK: workaround for gasPrice coming from nodes for EIP-1159 - this allows Oracle to work for now - case gasPrice } public init?(from decoder: Decoder) throws { @@ -147,9 +134,6 @@ extension EIP1559Envelope { self.to = ethAddr } - // MARK: workaround for gasPrice coming from nodes for EIP-1159 - this allows Oracle to work for now - self.gasPrice = try container.decodeHexIfPresent(BigUInt.self, forKey: .gasPrice) ?? 5000000000 - self.value = try container.decodeHexIfPresent(BigUInt.self, forKey: .value) ?? 0 self.maxPriorityFeePerGas = try container.decodeHexIfPresent(BigUInt.self, forKey: .maxPriorityFeePerGas) ?? 0 self.maxFeePerGas = try container.decodeHexIfPresent(BigUInt.self, forKey: .maxFeePerGas) ?? 0 @@ -212,7 +196,7 @@ extension EIP1559Envelope { // swiftlint:disable force_unwrapping switch rlpItem[RlpKey.destination.rawValue]!.content { - // swiftlint:enable force_unwrapping + // swiftlint:enable force_unwrapping case .noItem: self.to = EthereumAddress.contractDeploymentAddress() case .data(let addressData): diff --git a/Sources/web3swift/Transaction/EthereumMetadata.swift b/Sources/web3swift/Transaction/EthereumMetadata.swift new file mode 100644 index 000000000..4aec23e0d --- /dev/null +++ b/Sources/web3swift/Transaction/EthereumMetadata.swift @@ -0,0 +1,57 @@ +// Package: web3swift +// Created by Alex Vlasov. +// Copyright © 2018 Alex Vlasov. All rights reserved. +// +// Additions for metadata by Mark Loit 2022 + +import Foundation +import BigInt + +/// This structure holds additional data +/// returned by nodes when reading a transaction +/// from the blockchain. The data here is not +/// part of the transaction itself +public struct EthereumMetadata { + + /// hash for the block that contains this transaction on chain + var blockHash: Data? + + /// block number for the block containing this transaction on chain + var blockNumber: BigUInt? + + /// index for this transaction within the containing block + var transactionIndex: UInt? + + /// hash for this transaction as returned by the node [not computed] + /// this can be used for validation against the computed hash returned + /// by the transaction envelope. + var transactionHash: Data? + + /// gasPrice value returned by the node + /// note this is a duplicate value for legacy and EIP-2930 transaction types + /// but is included here since EIP-1559 does not contain a `gasPrice`, but + /// nodes still report the value. + var gasPrice: BigUInt? +} + +public extension EthereumMetadata { + private enum CodingKeys: String, CodingKey { + case blockHash + case blockNumber + case transactionIndex + case transactionHash + case gasPrice + } + + /// since metadata realistically can only come when a transaction is created from + /// JSON returned by a node, we only provide an intializer from JSON + init(from decoder: Decoder) throws { + let container = try decoder.container(keyedBy: CodingKeys.self) + + self.blockHash = try container.decodeHexIfPresent(Data.self, forKey: .blockHash) + self.transactionHash = try container.decodeHexIfPresent(Data.self, forKey: .transactionHash) + self.transactionIndex = try container.decodeHexIfPresent(UInt.self, forKey: .transactionIndex) + self.blockNumber = try container.decodeHexIfPresent(BigUInt.self, forKey: .blockNumber) + self.gasPrice = try container.decodeHexIfPresent(BigUInt.self, forKey: .gasPrice) + } +} diff --git a/Sources/web3swift/Transaction/EthereumTransaction.swift b/Sources/web3swift/Transaction/EthereumTransaction.swift index 6a0cdf6a0..070e8fbb0 100755 --- a/Sources/web3swift/Transaction/EthereumTransaction.swift +++ b/Sources/web3swift/Transaction/EthereumTransaction.swift @@ -14,6 +14,10 @@ public struct EthereumTransaction: CustomStringConvertible { /// and type specific implementation internal var envelope: AbstractEnvelope + /// storage container for additional metadata returned by the node + /// when a transaction is decoded form a JSON stream + public var meta: EthereumMetadata? + // convenience accessors to the common envelope fields // everything else should come from getOpts/setOpts /// The type of the transacton being represented, see TransactionType enum @@ -227,6 +231,8 @@ extension EthereumTransaction: Decodable { public init(from decoder: Decoder) throws { guard let env = try EnvelopeFactory.createEnvelope(from: decoder) else { throw Web3Error.dataError } self.envelope = env + // capture any metadata that might be present + self.meta = try EthereumMetadata(from: decoder) } } @@ -330,10 +336,7 @@ extension EthereumTransaction { guard let env = self.envelope as? EIP2930Envelope else { preconditionFailure("Unable to downcast to EIP2930Envelope") } return env.parameters.gasPrice ?? 0 case .eip1559: - // MARK: workaround for gasPrice coming from nodes for EIP-1159 - this allows Oracle to work for now - guard let env = self.envelope as? EIP1559Envelope else { preconditionFailure("Unable to downcast to EIP1559Envelope") } - return env.parameters.gasPrice ?? 0 - // preconditionFailure("EIP1559Envelope has no member gasPrice") + preconditionFailure("EIP1559Envelope has no member gasPrice") } } set(value) { diff --git a/Sources/web3swift/Web3/Web3+GasOracle.swift b/Sources/web3swift/Web3/Web3+GasOracle.swift index 1aa13e025..14196e0e4 100644 --- a/Sources/web3swift/Web3/Web3+GasOracle.swift +++ b/Sources/web3swift/Web3/Web3+GasOracle.swift @@ -139,7 +139,7 @@ extension Web3 { return transaction } } - .map { $0.gasPrice } + .map { $0.meta?.gasPrice ?? 0 } return calculatePercentiles(for: lastNthBlockGasPrice) } diff --git a/web3swift.xcodeproj/project.pbxproj b/web3swift.xcodeproj/project.pbxproj index f981b6f77..3d919dc39 100755 --- a/web3swift.xcodeproj/project.pbxproj +++ b/web3swift.xcodeproj/project.pbxproj @@ -202,6 +202,7 @@ 6049F416280616FC00DFE624 /* LegacyEnvelope.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6049F40F280616FC00DFE624 /* LegacyEnvelope.swift */; }; 6049F4182806171300DFE624 /* Web3+Signing.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6049F4172806171300DFE624 /* Web3+Signing.swift */; }; 604FA4FF27ECBDC80021108F /* DataConversionTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 604FA4FE27ECBDC80021108F /* DataConversionTests.swift */; }; + 60C1786E2809BA0C0083F064 /* EthereumMetadata.swift in Sources */ = {isa = PBXBuildFile; fileRef = 60C1786D2809BA0C0083F064 /* EthereumMetadata.swift */; }; CB50A52827060BD600D7E39B /* EIP712Tests.swift in Sources */ = {isa = PBXBuildFile; fileRef = CB50A52727060BD600D7E39B /* EIP712Tests.swift */; }; D606A56B279F5D59003643ED /* web3swiftDecodeSolidityErrorType.swift in Sources */ = {isa = PBXBuildFile; fileRef = D606A56A279F5D59003643ED /* web3swiftDecodeSolidityErrorType.swift */; }; D6A3D9B827F1E785009E3BCF /* ABIEncoderSoliditySha3Test.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6A3D9B727F1E785009E3BCF /* ABIEncoderSoliditySha3Test.swift */; }; @@ -433,6 +434,7 @@ 6049F40F280616FC00DFE624 /* LegacyEnvelope.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LegacyEnvelope.swift; sourceTree = ""; }; 6049F4172806171300DFE624 /* Web3+Signing.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "Web3+Signing.swift"; sourceTree = ""; }; 604FA4FE27ECBDC80021108F /* DataConversionTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DataConversionTests.swift; sourceTree = ""; }; + 60C1786D2809BA0C0083F064 /* EthereumMetadata.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EthereumMetadata.swift; sourceTree = ""; }; CB50A52727060BD600D7E39B /* EIP712Tests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = EIP712Tests.swift; sourceTree = ""; }; CB50A52927060C5300D7E39B /* EIP712.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = EIP712.swift; sourceTree = ""; }; D606A56A279F5D59003643ED /* web3swiftDecodeSolidityErrorType.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = web3swiftDecodeSolidityErrorType.swift; sourceTree = ""; }; @@ -720,6 +722,7 @@ 6049F40F280616FC00DFE624 /* LegacyEnvelope.swift */, 3AA8153D2276E44100F5DB52 /* BloomFilter.swift */, 3AA8153F2276E44100F5DB52 /* EthereumTransaction.swift */, + 60C1786D2809BA0C0083F064 /* EthereumMetadata.swift */, ); path = Transaction; sourceTree = ""; @@ -1349,6 +1352,7 @@ 3AA815C12276E44100F5DB52 /* Web3+BrowserFunctions.swift in Sources */, 3AA815E82276E44100F5DB52 /* Extensions.swift in Sources */, 3AA815FE2276E44100F5DB52 /* Web3+ERC820.swift in Sources */, + 60C1786E2809BA0C0083F064 /* EthereumMetadata.swift in Sources */, 3AA815DB2276E44100F5DB52 /* Web3+TxPool.swift in Sources */, 3AA8160A2276E44100F5DB52 /* Web3+ERC1594.swift in Sources */, 3AA815FB2276E44100F5DB52 /* Promise+Web3+Personal+UnlockAccount.swift in Sources */,