├── Gemfile ├── codecov.yml ├── .slather.yml ├── TezosKit ├── Common │ ├── Models │ │ ├── Hex.swift │ │ ├── Address.swift │ │ ├── KeyChainWallet.swift │ │ └── SecureEnclaveWallet.swift │ ├── RPC │ │ ├── Header.swift │ │ └── RPC.swift │ ├── Michelson │ │ ├── ListMichelsonParameter.swift │ │ ├── StringMichelsonParameter.swift │ │ ├── NoneMichelsonParameter.swift │ │ ├── UnitMichelsonParameter.swift │ │ ├── BoolMichelsonParameter.swift │ │ ├── MichelsonAnnotation.swift │ │ ├── SomeMichelsonParameter.swift │ │ ├── LeftMichelsonParameter.swift │ │ ├── RawMichelineMichelsonParameter.swift │ │ ├── RightMichelsonParameter.swift │ │ ├── AddressMichelsonParameter.swift │ │ ├── MichelsonComparable.swift │ │ ├── TimestampMichelsonParameter.swift │ │ ├── PairMichelsonParameter.swift │ │ ├── BytesMichelsonParameter.swift │ │ ├── SignatureMichelsonParameter.swift │ │ ├── ChainIDMichelsonParameter.swift │ │ ├── KeyHashMichelsonParameter.swift │ │ ├── KeyMichelsonParameter.swift │ │ ├── MichelsonParameter.swift │ │ ├── AbstractMichelsonParameter.swift │ │ ├── IntMichelsonParameter.swift │ │ └── NatMichelsonParameter.swift │ ├── TezosKitError.swift │ └── Services │ │ └── Logger.swift ├── Conseil │ ├── Models │ │ ├── ConseilPlatform.swift │ │ ├── ConseilNetwork.swift │ │ ├── ConseilEntity.swift │ │ └── ConseilQuery.swift │ └── RPC │ │ ├── ResponseAdapters │ │ └── TransactionsResponseAdapter.swift │ │ ├── ConseilQueryRPC.swift │ │ ├── GetReceivedTransactions.RPC.swift │ │ ├── GetSentTransactionsRPC.swift │ │ ├── GetOriginatedContractsRPC.swift │ │ └── GetRecievedSmartContractTransactionsRPC.swift ├── Crypto │ ├── Sodium+TezosCrypto.swift │ ├── EllipticalCurve.swift │ ├── PublicKeyProtocol.swift │ ├── EllipticCurveKeyPair │ │ └── README.md │ ├── Base58+TezosCrypto.swift │ ├── Prefixes.swift │ └── CryptoUtils.swift ├── TezosNode │ ├── Models │ │ ├── GasLimitPolicy.swift │ │ ├── SimulationResult.swift │ │ ├── PeriodKind.swift │ │ ├── OperationFeePolicy.swift │ │ ├── Operation │ │ │ ├── OperationKind.swift │ │ │ ├── Operation.swift │ │ │ ├── OperationWithCounter.swift │ │ │ ├── RevealOperation.swift │ │ │ ├── AbstractOperation.swift │ │ │ ├── TransactionOperation.swift │ │ │ ├── OriginationOperation.swift │ │ │ ├── SmartContractInvocationOperation.swift │ │ │ ├── DelegationOperation.swift │ │ │ └── OperationResponse.swift │ │ ├── TezosProtocol.swift │ │ ├── ForgingPolicy.swift │ │ ├── OperationFees.swift │ │ └── OperationMetadata.swift │ ├── RPC │ │ ├── GetExpectedQuorumRPC.swift │ │ ├── GetChainHeadHashRPC.swift │ │ ├── GetBallotsListRPC.swift │ │ ├── GetBallotsRPC.swift │ │ ├── GetChainHeadRPC.swift │ │ ├── GetCurrentPeriodKindRPC.swift │ │ ├── GetProposalsListRPC.swift │ │ ├── GetProposalUnderEvaluationRPC.swift │ │ ├── GetVotingDelegateRightsRPC.swift │ │ ├── ResponseAdapters │ │ │ ├── TezResponseAdapter.swift │ │ │ ├── IntegerResponseAdapter.swift │ │ │ ├── ResponseAdapter.swift │ │ │ ├── PeriodKindResponseAdapter.swift │ │ │ ├── AbstractResponseAdapter.swift │ │ │ ├── JSONDictionaryResponseAdapter.swift │ │ │ ├── JSONArrayResponseAdapter.swift │ │ │ ├── StringResponseAdapter.swift │ │ │ ├── PackDataResponseAdapter.swift │ │ │ └── SimulationResultResponseAdapter.swift │ │ ├── GetAddressBalanceRPC.swift │ │ ├── GetAddressCounterRPC.swift │ │ ├── GetAddressDelegateRPC.swift │ │ ├── GetAddressManagerKeyRPC.swift │ │ ├── GetContractStorageRPC.swift │ │ ├── InjectOperationRPC.swift │ │ ├── PackDataRPC.swift │ │ ├── RunOperationRPC.swift │ │ ├── GetBigMapValueByIDRPC.swift │ │ ├── ForgeOperationRPC.swift │ │ ├── PreapplyOperationRPC.swift │ │ ├── Payload │ │ │ ├── SignedProtocolOperationPayload.swift │ │ │ ├── RunOperationPayload.swift │ │ │ ├── SignedOperationPayload.swift │ │ │ ├── OperationPayload.swift │ │ │ └── PackDataPayload.swift │ │ └── GetBigMapValueRPC.swift │ └── Services │ │ ├── SigningService.swift │ │ ├── InjectionService.swift │ │ └── OperationPayloadFactory.swift ├── Info.plist └── Utils │ ├── JailbreakUtils.swift │ ├── MnemonicUtil.swift │ └── JSONUtils.swift ├── Tests ├── UnitTests │ └── TezosKit │ │ ├── TezosNodeClientTests.swift │ │ ├── CodingUtilTest.swift │ │ ├── GetChainHeadRPCTest.swift │ │ ├── GetBallotsRPCTest.swift │ │ ├── GetChainHeadHashRPCTest.swift │ │ ├── GetBallotsListRPCTest.swift │ │ ├── GetProposalsListRPCTest.swift │ │ ├── GetExpectedQuorumRPCTest.swift │ │ ├── OperationWithCounterTest.swift │ │ ├── InjectOperationRPCTest.swift │ │ ├── GetCurrentPeriodKindRPCTest.swift │ │ ├── GetVotingDelegateRightsRPCTest.swift │ │ ├── GetProposalUnderEvaluationRPCTest.swift │ │ ├── GetDelegateRPCTest.swift │ │ ├── RPCTest.swift │ │ ├── GetAddressBalanceRPCTest.swift │ │ ├── GetAddressCounterRPCTest.swift │ │ ├── GetContractStorageRPCTest.swift │ │ ├── ConseilEntityTest.swift │ │ ├── SignedProtocolOperationPayload.swift │ │ ├── ConseilNetworkTest.swift │ │ ├── ConseilPlatformTest.swift │ │ ├── SignedOperationPayloadTest.swift │ │ ├── MichelsonAnnotationTests.swift │ │ ├── SigningServiceTests.swift │ │ ├── RunOperationRPCTest.swift │ │ ├── GetAddressManagerKeyRPCTest.swift │ │ ├── ForgeOperationRPCTest.swift │ │ ├── PackDataResponseAdapterTest.swift │ │ ├── GetBigMapValueRPCTest.swift │ │ ├── Info.plist │ │ ├── PreapplyOperationRPCTest.swift │ │ ├── RevealOperationTest.swift │ │ ├── TransactionOperationTest.swift │ │ ├── FeeEstimatorTest.swift │ │ ├── JSONArrayResponseAdapterTest.swift │ │ ├── StringResponseAdapterTest.swift │ │ ├── InjectionServiceTest.swift │ │ ├── JSONDictionaryResponseAdapterTest.swift │ │ ├── TokenContractClientTests.swift │ │ ├── AbstractOperationTest.swift │ │ ├── IntegerResponseAdapterTest.swift │ │ ├── TezResponseAdapterTest.swift │ │ ├── PeriodKindResponseAdapterTest.swift │ │ ├── DelegationOperationTest.swift │ │ ├── TransactionsResponseAdapterTest.swift │ │ ├── TransactionTest.swift │ │ ├── ForgingServiceTests.swift │ │ ├── JSONUtilsTest.swift │ │ └── MnemonicUtilsTest.swift ├── IntegrationTests │ ├── TezosKit │ │ ├── Info.plist │ │ └── ConseilClientIntegrationTests.swift │ ├── Extensions │ │ └── PromiseKit │ │ │ └── ConseilClientIntegrationTests+Promises.swift │ └── ManagerContractClientIntegrationTest.swift └── Common │ └── TestHelpers.swift ├── TezosKit.xcodeproj └── project.xcworkspace │ ├── contents.xcworkspacedata │ └── xcshareddata │ └── IDEWorkspaceChecks.plist ├── Examples ├── TezosKitExample.playground │ ├── contents.xcplayground │ └── Contents.swift └── SecureEnclave │ ├── Enclave.entitlements │ ├── BackgroundHighlightedButton.swift │ ├── Base.lproj │ ├── Main.storyboard │ └── LaunchScreen.storyboard │ ├── Info.plist │ └── SecureEnclaveAppDelegate.swift ├── docs ├── AthensProtocolFees.md ├── FutureWork.md ├── README.md ├── AdvancedFunctionality.md ├── PromiseKit.md ├── Fees.md ├── Operations.md ├── Networking.md └── Testing.md ├── TezosKit.xcworkspace ├── xcshareddata │ └── IDEWorkspaceChecks.plist └── contents.xcworkspacedata ├── Cartfile ├── Cartfile.resolved ├── Extensions └── PromiseKit │ ├── Common │ └── NetworkClient+Promises.swift │ └── Conseil │ └── ConseilClient+Promises.swift ├── .gitignore ├── LICENSE ├── .swiftlint.yml ├── TezosKit.podspec ├── project.yml └── .travis.yml /Gemfile: -------------------------------------------------------------------------------- 1 | source 'https://rubygems.org' 2 | 3 | gem 'slather' 4 | -------------------------------------------------------------------------------- /codecov.yml: -------------------------------------------------------------------------------- 1 | # List of ignore directories for code coverage. 2 | ignore: 3 | - "Tests" 4 | -------------------------------------------------------------------------------- /.slather.yml: -------------------------------------------------------------------------------- 1 | coverage_service: cobertura_xml 2 | xcodeproj: ./TezosKit.xcodeproj 3 | scheme: TezosKit_iOS 4 | output_directory: ./ 5 | -------------------------------------------------------------------------------- /TezosKit/Common/Models/Hex.swift: -------------------------------------------------------------------------------- 1 | // Copyright Keefer Taylor, 2019. 2 | 3 | import Foundation 4 | 5 | public typealias Hex = String 6 | -------------------------------------------------------------------------------- /TezosKit/Common/Models/Address.swift: -------------------------------------------------------------------------------- 1 | // Copyright Keefer Taylor, 2019. 2 | 3 | import Foundation 4 | 5 | public typealias Address = String 6 | -------------------------------------------------------------------------------- /Tests/UnitTests/TezosKit/TezosNodeClientTests.swift: -------------------------------------------------------------------------------- 1 | // Copyright Keefer Taylor, 2019 2 | 3 | @testable import TezosKit 4 | import XCTest 5 | 6 | class TezosNodeClientTests: XCTestCase { 7 | } 8 | -------------------------------------------------------------------------------- /TezosKit/Conseil/Models/ConseilPlatform.swift: -------------------------------------------------------------------------------- 1 | // Copyright Keefer Taylor, 2019. 2 | 3 | /// Platforms supported by Conseil. 4 | public enum ConseilPlatform: String, CaseIterable { 5 | case tezos 6 | } 7 | -------------------------------------------------------------------------------- /TezosKit.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /Examples/TezosKitExample.playground/contents.xcplayground: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /TezosKit/Crypto/Sodium+TezosCrypto.swift: -------------------------------------------------------------------------------- 1 | // Copyright Keefer Taylor, 2019. 2 | 3 | import Foundation 4 | import Sodium 5 | 6 | /// Wrapper the sodium library which allows a single instance to be shared. 7 | extension Sodium { 8 | public static let shared = Sodium() 9 | } 10 | -------------------------------------------------------------------------------- /TezosKit/TezosNode/Models/GasLimitPolicy.swift: -------------------------------------------------------------------------------- 1 | // Copyright Keefer Taylor, 2019. 2 | 3 | import Foundation 4 | 5 | /// A policy to apply when deciding what gas limit to use. 6 | public enum GasLimitPolicy { 7 | case estimate 8 | case `default` 9 | case custom(Int) 10 | } 11 | -------------------------------------------------------------------------------- /TezosKit/TezosNode/Models/SimulationResult.swift: -------------------------------------------------------------------------------- 1 | // Copyright Keefer Taylor, 2019. 2 | 3 | import Foundation 4 | 5 | /// The result of simulating an operation. 6 | public struct SimulationResult { 7 | public let consumedGas: Int 8 | public let consumedStorage: Int 9 | } 10 | -------------------------------------------------------------------------------- /docs/AthensProtocolFees.md: -------------------------------------------------------------------------------- 1 | # Fees for Athens Protocol (Protocol 004) 2 | 3 | Nomadic Labs (Athens protocol developers) has published operation fees here: http://tezos.gitlab.io/master/protocols/004_Pt24m4xi.html. 4 | 5 | The history for this file contains my previous recommended values. 6 | -------------------------------------------------------------------------------- /TezosKit/Conseil/Models/ConseilNetwork.swift: -------------------------------------------------------------------------------- 1 | // Copyright Keefer Taylor, 2019 2 | 3 | /// Networks supported by Conseil. 4 | public enum ConseilNetwork: String, CaseIterable { 5 | case zeronet 6 | case alphanet 7 | case babylonnet 8 | case carthagenet 9 | case mainnet 10 | } 11 | -------------------------------------------------------------------------------- /TezosKit/Crypto/EllipticalCurve.swift: -------------------------------------------------------------------------------- 1 | // Copyright Keefer Taylor, 2019. 2 | 3 | import Foundation 4 | 5 | /// Elliptical curves that can be used in elliptical curve cryptographic operations. 6 | public enum EllipticalCurve { 7 | case ed25519 8 | case secp256k1 9 | case p256 10 | } 11 | -------------------------------------------------------------------------------- /TezosKit.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /TezosKit.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /docs/FutureWork.md: -------------------------------------------------------------------------------- 1 | # Future Work 2 | A list of planned improvements to TezosKit. 3 | ## Fee Estimation 4 | Estimate fees and gas limits based on running an operation 5 | ## Local Forging 6 | Forge operations locally rather than forging on the remote node. 7 | ## Secure Enclave Support 8 | Build out support for signing using the secure enclave. 9 | -------------------------------------------------------------------------------- /TezosKit/TezosNode/Models/PeriodKind.swift: -------------------------------------------------------------------------------- 1 | // Copyright Keefer Taylor, 2018 2 | 3 | import Foundation 4 | 5 | /// An enum representing the current period for protocol upgrades. 6 | public enum PeriodKind: String { 7 | case proposal 8 | case testingVote = "testing_vote" 9 | case testing 10 | case promotionVote = "promotion_vote" 11 | } 12 | -------------------------------------------------------------------------------- /TezosKit/Crypto/PublicKeyProtocol.swift: -------------------------------------------------------------------------------- 1 | // Copyright Keefer Taylor, 2020 2 | 3 | import Foundation 4 | 5 | /// Opaque representation of a public key in TezosKit. 6 | public protocol PublicKeyProtocol { 7 | var base58CheckRepresentation: String { get } 8 | var signingCurve: EllipticalCurve { get } 9 | var publicKeyHash: String { get } 10 | } 11 | -------------------------------------------------------------------------------- /Cartfile: -------------------------------------------------------------------------------- 1 | github "attaswift/BigInt" ~> 5.0.0 2 | github "mxcl/PromiseKit" ~> 6.13.1 3 | github "keefertaylor/Base58Swift" ~> 2.1.14 4 | github "keefertaylor/MnemonicKit" ~> 1.3.11 5 | github "jedisct1/swift-sodium" ~> 0.8.0 6 | github "krzyzanowskim/CryptoSwift" ~> 1.3.0 7 | github "keefertaylor/secp256k1.swift" ~> 8.0.6 8 | github "thii/DTTJailbreakDetection" "master" 9 | -------------------------------------------------------------------------------- /TezosKit/Common/RPC/Header.swift: -------------------------------------------------------------------------------- 1 | // Copyright Keefer Taylor, 2019 2 | 3 | import Foundation 4 | 5 | /// An encapsulation of headers to use in an RPC. 6 | public struct Header { 7 | public static let contentTypeApplicationJSON = Header(field: "Content-Type", value: "application/json") 8 | 9 | public let field: String 10 | public let value: String 11 | } 12 | -------------------------------------------------------------------------------- /TezosKit/Common/Michelson/ListMichelsonParameter.swift: -------------------------------------------------------------------------------- 1 | // Copyright Keefer Taylor, 2020. 2 | 3 | import Foundation 4 | 5 | public class ListMichelsonParameter: AbstractMichelsonParameter { 6 | public init(args: [MichelsonParameter]) { 7 | let argArray = args.map { $0.networkRepresentation } 8 | super.init(networkRepresentation: argArray) 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /Tests/UnitTests/TezosKit/CodingUtilTest.swift: -------------------------------------------------------------------------------- 1 | // Copyright Keefer Taylor, 2019. 2 | 3 | import TezosKit 4 | import XCTest 5 | 6 | class CryptoUtilTest: XCTestCase { 7 | public func testHexToBin() { 8 | XCTAssertEqual(CryptoUtils.hexToBin("1234"), [18, 52]) 9 | } 10 | 11 | public func testBinToHex() { 12 | XCTAssertEqual(CryptoUtils.binToHex([18, 52]), "1234") 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /TezosKit.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /Cartfile.resolved: -------------------------------------------------------------------------------- 1 | github "attaswift/BigInt" "5.1.0" 2 | github "jedisct1/swift-sodium" "0.8.0" 3 | github "keefertaylor/Base58Swift" "2.1.14" 4 | github "keefertaylor/MnemonicKit" "1.3.12" 5 | github "keefertaylor/secp256k1.swift" "8.0.6" 6 | github "krzyzanowskim/CryptoSwift" "1.3.1" 7 | github "mxcl/PromiseKit" "6.13.1" 8 | github "thii/DTTJailbreakDetection" "35c3fafabff7f28f1142471ed31a0616a024ae2f" 9 | -------------------------------------------------------------------------------- /Examples/SecureEnclave/Enclave.entitlements: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | keychain-access-groups 6 | 7 | $(AppIdentifierPrefix)com.keefertaylor.TezosKit.SecureEnclave 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /Tests/UnitTests/TezosKit/GetChainHeadRPCTest.swift: -------------------------------------------------------------------------------- 1 | // Copyright Keefer Taylor, 2018 2 | 3 | import TezosKit 4 | import XCTest 5 | 6 | class GetChainHeadRPCTest: XCTestCase { 7 | public func testGetChainHeadRPC() { 8 | let rpc = GetChainHeadRPC() 9 | 10 | XCTAssertEqual(rpc.endpoint, "/chains/main/blocks/head") 11 | XCTAssertNil(rpc.payload) 12 | XCTAssertFalse(rpc.isPOSTRequest) 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /TezosKit/TezosNode/RPC/GetExpectedQuorumRPC.swift: -------------------------------------------------------------------------------- 1 | // Copyright Keefer Taylor, 2018 2 | 3 | import Foundation 4 | 5 | /// An RPC which will retrieve the expected quorum. 6 | public class GetExpectedQuorumRPC: RPC { 7 | public init() { 8 | let endpoint = "chains/main/blocks/head/votes/current_quorum" 9 | super.init(endpoint: endpoint, responseAdapterClass: IntegerResponseAdapter.self) 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /Tests/UnitTests/TezosKit/GetBallotsRPCTest.swift: -------------------------------------------------------------------------------- 1 | // Copyright Keefer Taylor, 2018 2 | 3 | import TezosKit 4 | import XCTest 5 | 6 | class GetBallotsRPCTest: XCTestCase { 7 | public func testGetBallotsRPC() { 8 | let rpc = GetBallotsRPC() 9 | 10 | XCTAssertEqual(rpc.endpoint, "chains/main/blocks/head/votes/ballots") 11 | XCTAssertNil(rpc.payload) 12 | XCTAssertFalse(rpc.isPOSTRequest) 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /TezosKit/TezosNode/RPC/GetChainHeadHashRPC.swift: -------------------------------------------------------------------------------- 1 | // Copyright Keefer Taylor, 2018 2 | 3 | import Foundation 4 | 5 | /// An RPC which will retrieve the hash of the head of the main chain. 6 | public class GetChainHeadHashRPC: RPC { 7 | public init() { 8 | let endpoint = "chains/main/blocks/head/hash" 9 | super.init(endpoint: endpoint, responseAdapterClass: StringResponseAdapter.self) 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /Tests/UnitTests/TezosKit/GetChainHeadHashRPCTest.swift: -------------------------------------------------------------------------------- 1 | // Copyright Keefer Taylor, 2018 2 | 3 | import TezosKit 4 | import XCTest 5 | 6 | class GetChainHeadHashRPCTest: XCTestCase { 7 | public func testGetChainHeadHashRPC() { 8 | let rpc = GetChainHeadHashRPC() 9 | 10 | XCTAssertEqual(rpc.endpoint, "chains/main/blocks/head/hash") 11 | XCTAssertNil(rpc.payload) 12 | XCTAssertFalse(rpc.isPOSTRequest) 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /Tests/UnitTests/TezosKit/GetBallotsListRPCTest.swift: -------------------------------------------------------------------------------- 1 | // Copyright Keefer Taylor, 2018 2 | 3 | import TezosKit 4 | import XCTest 5 | 6 | class GetBallotsListRPCTest: XCTestCase { 7 | public func testGetBallotsListRPC() { 8 | let rpc = GetBallotsListRPC() 9 | 10 | XCTAssertEqual(rpc.endpoint, "/chains/main/blocks/head/votes/ballot_list") 11 | XCTAssertNil(rpc.payload) 12 | XCTAssertFalse(rpc.isPOSTRequest) 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /Tests/UnitTests/TezosKit/GetProposalsListRPCTest.swift: -------------------------------------------------------------------------------- 1 | // Copyright Keefer Taylor, 2018 2 | 3 | import TezosKit 4 | import XCTest 5 | 6 | class GetProposalsListRPCTest: XCTestCase { 7 | public func testGetProposalsListRPC() { 8 | let rpc = GetProposalsListRPC() 9 | 10 | XCTAssertEqual(rpc.endpoint, "chains/main/blocks/head/votes/proposals") 11 | XCTAssertNil(rpc.payload) 12 | XCTAssertFalse(rpc.isPOSTRequest) 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /TezosKit/TezosNode/RPC/GetBallotsListRPC.swift: -------------------------------------------------------------------------------- 1 | // Copyright Keefer Taylor, 2018 2 | 3 | import Foundation 4 | 5 | /// An RPC which will retrieve ballots cast so far during a voting period. 6 | public class GetBallotsListRPC: RPC<[[String: Any]]> { 7 | public init() { 8 | let endpoint = "/chains/main/blocks/head/votes/ballot_list" 9 | super.init(endpoint: endpoint, responseAdapterClass: JSONArrayResponseAdapter.self) 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /TezosKit/TezosNode/RPC/GetBallotsRPC.swift: -------------------------------------------------------------------------------- 1 | // Copyright Keefer Taylor, 2018 2 | 3 | import Foundation 4 | 5 | /// An RPC which will retrieve the sum of ballots cast so far during a voting period. 6 | public class GetBallotsRPC: RPC<[String: Any]> { 7 | public init() { 8 | let endpoint = "chains/main/blocks/head/votes/ballots" 9 | super.init(endpoint: endpoint, responseAdapterClass: JSONDictionaryResponseAdapter.self) 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /TezosKit/TezosNode/RPC/GetChainHeadRPC.swift: -------------------------------------------------------------------------------- 1 | // Copyright Keefer Taylor, 2018 2 | 3 | import Foundation 4 | 5 | /// An RPC which will retrieve a JSON dictionary of info about the head of the current chain. 6 | public class GetChainHeadRPC: RPC<[String: Any]> { 7 | public init() { 8 | let endpoint = "/chains/main/blocks/head" 9 | super.init(endpoint: endpoint, responseAdapterClass: JSONDictionaryResponseAdapter.self) 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /TezosKit/Common/Michelson/StringMichelsonParameter.swift: -------------------------------------------------------------------------------- 1 | // Copyright Keefer Taylor, 2019. 2 | 3 | import Foundation 4 | 5 | /// A representation of a string parameter in Michelson. 6 | public class StringMichelsonParameter: AbstractMichelsonParameter { 7 | public init(string: String, annotations: [MichelsonAnnotation]? = nil) { 8 | super.init(networkRepresentation: [MichelineConstants.string: string], annotations: annotations) 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /TezosKit/TezosNode/Models/OperationFeePolicy.swift: -------------------------------------------------------------------------------- 1 | // Copyright Keefer Taylor, 2019. 2 | 3 | import Foundation 4 | 5 | /// A policy that determines how to apply fees on an operation. 6 | public enum OperationFeePolicy { 7 | /// Use the default fees provided by TezosKit. 8 | case `default` 9 | 10 | /// Use custom fees in the associated value. 11 | case custom(OperationFees) 12 | 13 | /// Estimate the fees. 14 | case estimate 15 | } 16 | -------------------------------------------------------------------------------- /TezosKit/TezosNode/RPC/GetCurrentPeriodKindRPC.swift: -------------------------------------------------------------------------------- 1 | // Copyright Keefer Taylor, 2018 2 | 3 | import Foundation 4 | 5 | /// An RPC which will retrieve the current period kind for voting. 6 | public class GetCurrentPeriodKindRPC: RPC { 7 | public init() { 8 | let endpoint = "chains/main/blocks/head/votes/current_period_kind" 9 | super.init(endpoint: endpoint, responseAdapterClass: PeriodKindResponseAdapter.self) 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /TezosKit/TezosNode/RPC/GetProposalsListRPC.swift: -------------------------------------------------------------------------------- 1 | // Copyright Keefer Taylor, 2018 2 | 3 | import Foundation 4 | 5 | /// An RPC which will retrieve a list of proposals with number of supporters. 6 | public class GetProposalsListRPC: RPC<[[String: Any]]> { 7 | public init() { 8 | let endpoint = "chains/main/blocks/head/votes/proposals" 9 | super.init(endpoint: endpoint, responseAdapterClass: JSONArrayResponseAdapter.self) 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /TezosKit/TezosNode/RPC/GetProposalUnderEvaluationRPC.swift: -------------------------------------------------------------------------------- 1 | // Copyright Keefer Taylor, 2018 2 | 3 | import Foundation 4 | 5 | /// An RPC which will retrieve the current proposal under evaluation. 6 | public class GetProposalUnderEvaluationRPC: RPC { 7 | public init() { 8 | let endpoint = "chains/main/blocks/head/votes/current_proposal" 9 | super.init(endpoint: endpoint, responseAdapterClass: StringResponseAdapter.self) 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /Tests/UnitTests/TezosKit/GetExpectedQuorumRPCTest.swift: -------------------------------------------------------------------------------- 1 | // Copyright Keefer Taylor, 2018 2 | 3 | import TezosKit 4 | import XCTest 5 | 6 | class GetExpectedQuorumRPCTest: XCTestCase { 7 | public func testGetExpectedQuorumRPCTest() { 8 | let rpc = GetExpectedQuorumRPC() 9 | 10 | XCTAssertEqual(rpc.endpoint, "chains/main/blocks/head/votes/current_quorum") 11 | XCTAssertNil(rpc.payload) 12 | XCTAssertFalse(rpc.isPOSTRequest) 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /Tests/UnitTests/TezosKit/OperationWithCounterTest.swift: -------------------------------------------------------------------------------- 1 | // Copyright Keefer Taylor, 2019 2 | 3 | import TezosKit 4 | import XCTest 5 | 6 | class OperationWithCounterTest: XCTestCase { 7 | public func testDictionaryRepresentation() { 8 | let dictionaryRepresentation = OperationWithCounter.testOperationWithCounter.dictionaryRepresentation 9 | XCTAssertEqual(dictionaryRepresentation["counter"] as? String, String(Int.testAddressCounter)) 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /Tests/UnitTests/TezosKit/InjectOperationRPCTest.swift: -------------------------------------------------------------------------------- 1 | // Copyright Keefer Taylor, 2018 2 | 3 | import TezosKit 4 | import XCTest 5 | 6 | class InjectOperationRPCTest: XCTestCase { 7 | public func testInjectRPC() { 8 | let payload = "payload" 9 | let rpc = InjectionRPC(payload: payload) 10 | 11 | XCTAssertEqual(rpc.endpoint, "/injection/operation") 12 | XCTAssertEqual(rpc.payload, payload) 13 | XCTAssertTrue(rpc.isPOSTRequest) 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /TezosKit/TezosNode/Models/Operation/OperationKind.swift: -------------------------------------------------------------------------------- 1 | // Copyright Keefer Taylor, 2018 2 | 3 | import Foundation 4 | 5 | /// An enum representing all supported operation types. Raw values of the enum represent the string the Tezos blockchain 6 | /// expects for the "kind" attribute when forging / pre-applying / injecting operations 7 | public enum OperationKind: String { 8 | case transaction 9 | case reveal 10 | case delegation 11 | case origination 12 | } 13 | -------------------------------------------------------------------------------- /Tests/UnitTests/TezosKit/GetCurrentPeriodKindRPCTest.swift: -------------------------------------------------------------------------------- 1 | // Copyright Keefer Taylor, 2018 2 | 3 | import TezosKit 4 | import XCTest 5 | 6 | class GetCurrentPeriodKindRPCTest: XCTestCase { 7 | public func testGetCurrentPeriodKindRPC() { 8 | let rpc = GetCurrentPeriodKindRPC() 9 | 10 | XCTAssertEqual(rpc.endpoint, "chains/main/blocks/head/votes/current_period_kind") 11 | XCTAssertNil(rpc.payload) 12 | XCTAssertFalse(rpc.isPOSTRequest) 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /Tests/UnitTests/TezosKit/GetVotingDelegateRightsRPCTest.swift: -------------------------------------------------------------------------------- 1 | // Copyright Keefer Taylor, 2018 2 | 3 | import TezosKit 4 | import XCTest 5 | 6 | class GetVotingDelegateRightsRPCTest: XCTestCase { 7 | public func testGetVotingDelegateRightsRPC() { 8 | let rpc = GetVotingDelegateRightsRPC() 9 | 10 | XCTAssertEqual(rpc.endpoint, "chains/main/blocks/head/votes/listings") 11 | XCTAssertNil(rpc.payload) 12 | XCTAssertFalse(rpc.isPOSTRequest) 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /TezosKit/Common/Michelson/NoneMichelsonParameter.swift: -------------------------------------------------------------------------------- 1 | // Copyright Keefer Taylor, 2019. 2 | 3 | import Foundation 4 | 5 | /// A representation of a none param in Michelson. 6 | public class NoneMichelsonParameter: AbstractMichelsonParameter { 7 | public init(annotations: [MichelsonAnnotation]? = nil) { 8 | super.init( 9 | networkRepresentation: [ MichelineConstants.primitive: MichelineConstants.none ], 10 | annotations: annotations 11 | ) 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /TezosKit/TezosNode/RPC/GetVotingDelegateRightsRPC.swift: -------------------------------------------------------------------------------- 1 | // Copyright Keefer Taylor, 2018 2 | 3 | import Foundation 4 | 5 | /// An RPC which will retrieve a list of delegates with their voting weight, in number of rolls. 6 | public class GetVotingDelegateRightsRPC: RPC<[[String: Any]]> { 7 | public init() { 8 | let endpoint = "chains/main/blocks/head/votes/listings" 9 | super.init(endpoint: endpoint, responseAdapterClass: JSONArrayResponseAdapter.self) 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /TezosKit/Common/Michelson/UnitMichelsonParameter.swift: -------------------------------------------------------------------------------- 1 | // Copyright Keefer Taylor, 2019. 2 | 3 | import Foundation 4 | 5 | /// A representation of an unit parameter in Michelson. 6 | public class UnitMichelsonParameter: AbstractMichelsonParameter { 7 | public init(annotations: [MichelsonAnnotation]? = nil) { 8 | super.init( 9 | networkRepresentation: [ MichelineConstants.primitive: MichelineConstants.unit ], 10 | annotations: annotations 11 | ) 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /TezosKit/Conseil/RPC/ResponseAdapters/TransactionsResponseAdapter.swift: -------------------------------------------------------------------------------- 1 | // Copyright Keefer Taylor, 2019 2 | 3 | import Foundation 4 | 5 | public class TransactionsResponseAdapter: AbstractResponseAdapter<[Transaction]> { 6 | public override class func parse(input: Data) -> [Transaction]? { 7 | guard let transactions = JSONArrayResponseAdapter.parse(input: input) else { 8 | return nil 9 | } 10 | return transactions.compactMap { Transaction($0) } 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /TezosKit/TezosNode/Models/TezosProtocol.swift: -------------------------------------------------------------------------------- 1 | // Copyright Keefer Taylor, 2019. 2 | 3 | import Foundation 4 | 5 | /// Protocols supported by TezosKit. 6 | /// 7 | /// - Note: The Tezos network upgrades asynchronously and protocol versions between Zeronet, Alphanet and Mainnet are 8 | /// not necessarily the same. 9 | public enum TezosProtocol { 10 | case athens // Protocol version 4 11 | case babylon // Protocol version 5 12 | case carthage // Protocol version 6 13 | } 14 | -------------------------------------------------------------------------------- /TezosKit/Conseil/Models/ConseilEntity.swift: -------------------------------------------------------------------------------- 1 | // Copyright Keefer Taylor, 2019 2 | 3 | import Foundation 4 | 5 | /// Entities that may be queried in the Conseil API. 6 | public enum ConseilEntity: String, CaseIterable { 7 | case account = "accounts" 8 | case baker = "bakers" 9 | case ballots = "ballots" 10 | case block = "blocks" 11 | case fee = "fees" 12 | case operation = "operations" 13 | case operationGroup = "operation_groups" 14 | case proposal = "proposals" 15 | } 16 | -------------------------------------------------------------------------------- /Tests/UnitTests/TezosKit/GetProposalUnderEvaluationRPCTest.swift: -------------------------------------------------------------------------------- 1 | // Copyright Keefer Taylor, 2018 2 | 3 | import TezosKit 4 | import XCTest 5 | 6 | class GetProposalUnderEvaluationRPCTest: XCTestCase { 7 | public func testGetProposalUnderEvaluationRPC() { 8 | let rpc = GetProposalUnderEvaluationRPC() 9 | 10 | XCTAssertEqual(rpc.endpoint, "chains/main/blocks/head/votes/current_proposal") 11 | XCTAssertNil(rpc.payload) 12 | XCTAssertFalse(rpc.isPOSTRequest) 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /TezosKit/TezosNode/RPC/ResponseAdapters/TezResponseAdapter.swift: -------------------------------------------------------------------------------- 1 | // Copyright Keefer Taylor, 2018 2 | 3 | import Foundation 4 | 5 | /// Parse a given response as a string representing an amount of Tez. 6 | public class TezResponseAdapter: AbstractResponseAdapter { 7 | public override class func parse(input: Data) -> Tez? { 8 | guard let balanceString = StringResponseAdapter.parse(input: input) else { 9 | return nil 10 | } 11 | return Tez(balanceString) 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /Tests/UnitTests/TezosKit/GetDelegateRPCTest.swift: -------------------------------------------------------------------------------- 1 | // Copyright Keefer Taylor, 2018 2 | 3 | import TezosKit 4 | import XCTest 5 | 6 | class GetDelegateRPCTest: XCTestCase { 7 | public func testGetDelegateRPC() { 8 | let address = "abc123" 9 | let rpc = GetDelegateRPC(address: address) 10 | 11 | XCTAssertEqual(rpc.endpoint, "/chains/main/blocks/head/context/contracts/" + address + "/delegate") 12 | XCTAssertNil(rpc.payload) 13 | XCTAssertFalse(rpc.isPOSTRequest) 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /Tests/UnitTests/TezosKit/RPCTest.swift: -------------------------------------------------------------------------------- 1 | // Copyright Keefer Taylor, 2018 2 | 3 | @testable import TezosKit 4 | import XCTest 5 | 6 | class RPCTest: XCTestCase { 7 | public func testIsPOSTRequest() { 8 | let getRPC = RPC(endpoint: "a", responseAdapterClass: StringResponseAdapter.self) 9 | XCTAssertFalse(getRPC.isPOSTRequest) 10 | 11 | let postRPC = RPC(endpoint: "a", responseAdapterClass: StringResponseAdapter.self, payload: "abc") 12 | XCTAssertTrue(postRPC.isPOSTRequest) 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /Tests/UnitTests/TezosKit/GetAddressBalanceRPCTest.swift: -------------------------------------------------------------------------------- 1 | // Copyright Keefer Taylor, 2018 2 | 3 | import TezosKit 4 | import XCTest 5 | 6 | class GetAddressBalanceRPCTest: XCTestCase { 7 | public func testGetAddressBalanceRPC() { 8 | let address = "abc123" 9 | let rpc = GetAddressBalanceRPC(address: address) 10 | 11 | XCTAssertEqual(rpc.endpoint, "/chains/main/blocks/head/context/contracts/" + address + "/balance") 12 | XCTAssertNil(rpc.payload) 13 | XCTAssertFalse(rpc.isPOSTRequest) 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /Tests/UnitTests/TezosKit/GetAddressCounterRPCTest.swift: -------------------------------------------------------------------------------- 1 | // Copyright Keefer Taylor, 2018 2 | 3 | import TezosKit 4 | import XCTest 5 | 6 | class GetAddressCounterRPCTest: XCTestCase { 7 | public func testGetAddressCounterRPC() { 8 | let address = "abc123" 9 | let rpc = GetAddressCounterRPC(address: address) 10 | 11 | XCTAssertEqual(rpc.endpoint, "/chains/main/blocks/head/context/contracts/" + address + "/counter") 12 | XCTAssertNil(rpc.payload) 13 | XCTAssertFalse(rpc.isPOSTRequest) 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /Tests/UnitTests/TezosKit/GetContractStorageRPCTest.swift: -------------------------------------------------------------------------------- 1 | // Copyright Keefer Taylor, 2019. 2 | 3 | import TezosKit 4 | import XCTest 5 | 6 | final class GetContractStorageRPCTest: XCTestCase { 7 | func testGetContractStorageRPC() { 8 | let address = "abc123" 9 | let rpc = GetContractStorageRPC(address: address) 10 | 11 | XCTAssertEqual(rpc.endpoint, "/chains/main/blocks/head/context/contracts/\(address)/storage") 12 | XCTAssertNil(rpc.payload) 13 | XCTAssertFalse(rpc.isPOSTRequest) 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /TezosKit/TezosNode/Models/ForgingPolicy.swift: -------------------------------------------------------------------------------- 1 | // Copyright Keefer Taylor, 2019. 2 | 3 | import Foundation 4 | 5 | /// An enum defining policies used to forge operations. 6 | public enum ForgingPolicy { 7 | /// Always forge operations remotely on the node. 8 | case remote 9 | 10 | /// Always forge locally. Fail if the operation cannot be forged locally. 11 | case local 12 | 13 | /// Attempt to forge locally but fallback to remote forging if local forging is not possible. 14 | case localWithRemoteFallBack 15 | } 16 | -------------------------------------------------------------------------------- /TezosKit/TezosNode/RPC/GetAddressBalanceRPC.swift: -------------------------------------------------------------------------------- 1 | // Copyright Keefer Taylor, 2018 2 | 3 | import Foundation 4 | 5 | /// An RPC that will retrieve the balance of a given address. 6 | public class GetAddressBalanceRPC: RPC { 7 | /// - Parameter address: The address to retrieve info about. 8 | public init(address: Address) { 9 | let endpoint = "/chains/main/blocks/head/context/contracts/" + address + "/balance" 10 | super.init(endpoint: endpoint, responseAdapterClass: TezResponseAdapter.self) 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /TezosKit/TezosNode/RPC/GetAddressCounterRPC.swift: -------------------------------------------------------------------------------- 1 | // Copyright Keefer Taylor, 2018 2 | 3 | import Foundation 4 | 5 | /// A RPC which will retrieve the counter for an address. 6 | public class GetAddressCounterRPC: RPC { 7 | /// - Parameter address: The address to retrieve info about. 8 | public init(address: Address) { 9 | let endpoint = "/chains/main/blocks/head/context/contracts/" + address + "/counter" 10 | super.init(endpoint: endpoint, responseAdapterClass: IntegerResponseAdapter.self) 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /TezosKit/TezosNode/RPC/GetAddressDelegateRPC.swift: -------------------------------------------------------------------------------- 1 | // Copyright Keefer Taylor, 2018 2 | 3 | import Foundation 4 | 5 | /// An RPC that will retrieve the delegate of a given address. 6 | public class GetDelegateRPC: RPC { 7 | /// - Parameter address: The address to retrieve info about. 8 | public init(address: Address) { 9 | let endpoint = "/chains/main/blocks/head/context/contracts/" + address + "/delegate" 10 | super.init(endpoint: endpoint, responseAdapterClass: StringResponseAdapter.self) 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /TezosKit/TezosNode/RPC/ResponseAdapters/IntegerResponseAdapter.swift: -------------------------------------------------------------------------------- 1 | // Copyright Keefer Taylor, 2018 2 | 3 | import Foundation 4 | 5 | /// Parse a given response as a string representing an Integer. 6 | public class IntegerResponseAdapter: AbstractResponseAdapter { 7 | public override class func parse(input: Data) -> Int? { 8 | guard let parsedString = StringResponseAdapter.parse(input: input), 9 | let integer = Int(parsedString) else { 10 | return nil 11 | } 12 | return integer 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /TezosKit/Common/Michelson/BoolMichelsonParameter.swift: -------------------------------------------------------------------------------- 1 | // Copyright Keefer Taylor, 2019. 2 | 3 | import Foundation 4 | 5 | /// A representation of a boolean parameter in Michelson. 6 | public class BoolMichelsonParameter: AbstractMichelsonParameter { 7 | public init(bool: Bool, annotations: [MichelsonAnnotation]? = nil) { 8 | let stringRep = bool ? MichelineConstants.true : MichelineConstants.false 9 | super.init(networkRepresentation: [MichelineConstants.primitive: stringRep ], annotations: annotations) 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /TezosKit/TezosNode/RPC/GetAddressManagerKeyRPC.swift: -------------------------------------------------------------------------------- 1 | // Copyright Keefer Taylor, 2018 2 | 3 | import Foundation 4 | 5 | /// An RPC that will retrieve the manager key of a given address. 6 | public class GetAddressManagerKeyRPC: RPC { 7 | /// - Parameter address: The address to retrieve info about. 8 | public init(address: Address) { 9 | let endpoint = "/chains/main/blocks/head/context/contracts/" + address + "/manager_key" 10 | super.init(endpoint: endpoint, responseAdapterClass: StringResponseAdapter.self) 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /TezosKit/TezosNode/RPC/GetContractStorageRPC.swift: -------------------------------------------------------------------------------- 1 | // Copyright Keefer Taylor, 2019. 2 | 3 | import Foundation 4 | 5 | /// An RPC that will retrieve the storage of a given contract. 6 | public class GetContractStorageRPC: RPC<[String: Any]> { 7 | /// - Parameter address: The address to retrieve info about. 8 | public init(address: Address) { 9 | let endpoint = "/chains/main/blocks/head/context/contracts/\(address)/storage" 10 | super.init(endpoint: endpoint, responseAdapterClass: JSONDictionaryResponseAdapter.self) 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /Tests/UnitTests/TezosKit/ConseilEntityTest.swift: -------------------------------------------------------------------------------- 1 | // Copyright Keefer Taylor, 2019 2 | 3 | import TezosKit 4 | import XCTest 5 | 6 | final class ConseilEntityTest: XCTestCase { 7 | func testEntityRawValueURLEncoded() { 8 | for entity in ConseilEntity.allCases { 9 | guard let escapedEntity = 10 | entity.rawValue.addingPercentEncoding(withAllowedCharacters: CharacterSet.urlQueryAllowed) else { 11 | XCTFail() 12 | return 13 | } 14 | XCTAssertEqual(entity.rawValue, escapedEntity) 15 | } 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /TezosKit/TezosNode/RPC/ResponseAdapters/ResponseAdapter.swift: -------------------------------------------------------------------------------- 1 | // Copyright Keefer Taylor, 2018 2 | 3 | import Foundation 4 | 5 | /// A response adapter is an adapter that takes abstract data and turns it into a given type. 6 | /// Adapters can be used to transform arbitrary network bytes from RPCs into concrete model types. 7 | protocol ResponseAdapter { 8 | /// The type that this ResponseAdapter will parse to. 9 | associatedtype ParsedType 10 | 11 | /// Parse the given data to the given type. 12 | static func parse(input: Data) -> ParsedType? 13 | } 14 | -------------------------------------------------------------------------------- /Tests/UnitTests/TezosKit/SignedProtocolOperationPayload.swift: -------------------------------------------------------------------------------- 1 | // Copyright Keefer Taylor, 2019. 2 | 3 | import TezosKit 4 | import XCTest 5 | 6 | class SignedProtocolOperationPayloadTest: XCTestCase { 7 | public func testDictionaryRepresentation() { 8 | let dictionaryRepresentation = 9 | SignedProtocolOperationPayload.testSignedProtocolOperationPayload.dictionaryRepresentation 10 | 11 | XCTAssertEqual(dictionaryRepresentation.count, 1) 12 | XCTAssertEqual(dictionaryRepresentation[0]["protocol"] as? String, String.testProtocol) 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /TezosKit/TezosNode/RPC/ResponseAdapters/PeriodKindResponseAdapter.swift: -------------------------------------------------------------------------------- 1 | // Copyright Keefer Taylor, 2018 2 | 3 | import Foundation 4 | 5 | /// Parse a given response as a string representing an PeriodKind. 6 | public class PeriodKindResponseAdapter: AbstractResponseAdapter { 7 | public override class func parse(input: Data) -> PeriodKind? { 8 | guard let parsedString = StringResponseAdapter.parse(input: input), 9 | let periodKind = PeriodKind(rawValue: parsedString) else { 10 | return nil 11 | } 12 | return periodKind 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /Tests/UnitTests/TezosKit/ConseilNetworkTest.swift: -------------------------------------------------------------------------------- 1 | // Copyright Keefer Taylor, 2019 2 | 3 | import TezosKit 4 | import XCTest 5 | 6 | final class ConseilNetworkTest: XCTestCase { 7 | func testNetworkRawValueURLEncoded() { 8 | for network in ConseilNetwork.allCases { 9 | guard let escapedNetwork = network.rawValue.addingPercentEncoding( 10 | withAllowedCharacters: CharacterSet.urlQueryAllowed 11 | ) else { 12 | XCTFail() 13 | return 14 | } 15 | XCTAssertEqual(network.rawValue, escapedNetwork) 16 | } 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /TezosKit/Common/Michelson/MichelsonAnnotation.swift: -------------------------------------------------------------------------------- 1 | // Copyright Keefer Taylor, 2019. 2 | 3 | import Foundation 4 | 5 | /// A Michelson annotation. 6 | public class MichelsonAnnotation { 7 | public let value: String 8 | 9 | /// Initialize a new annotation. 10 | /// 11 | /// - Note: Michelson annotations must start with ':', '%', or '@'. 12 | public init?(annotation: String) { 13 | guard annotation.starts(with: ":") || annotation.starts(with: "%") || annotation.starts(with: "@") else { 14 | return nil 15 | } 16 | self.value = annotation 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /Tests/UnitTests/TezosKit/ConseilPlatformTest.swift: -------------------------------------------------------------------------------- 1 | // Copyright Keefer Taylor, 2019 2 | 3 | import TezosKit 4 | import XCTest 5 | 6 | final class ConseilPlatformTest: XCTestCase { 7 | func testPlatformRawValueURLEncoded() { 8 | for platform in ConseilPlatform.allCases { 9 | guard let escapedPlatform = platform.rawValue.addingPercentEncoding( 10 | withAllowedCharacters: CharacterSet.urlQueryAllowed 11 | ) else { 12 | XCTFail() 13 | return 14 | } 15 | XCTAssertEqual(platform.rawValue, escapedPlatform) 16 | } 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /TezosKit/TezosNode/Services/SigningService.swift: -------------------------------------------------------------------------------- 1 | // Copyright Keefer Taylor, 2019. 2 | 3 | import Base58Swift 4 | import Foundation 5 | 6 | /// An opaque object which implements public key cryptography functions. 7 | public protocol SignatureProvider { 8 | func sign(_ hex: String) -> [UInt8]? 9 | var publicKey: PublicKeyProtocol { get } 10 | } 11 | 12 | /// Manages signing of transactions. 13 | public enum SigningService { 14 | public static func sign(_ hex: String, with signatureProvider: SignatureProvider) -> [UInt8]? { 15 | return signatureProvider.sign(hex) 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /Tests/UnitTests/TezosKit/SignedOperationPayloadTest.swift: -------------------------------------------------------------------------------- 1 | // Copyright Keefer Taylor, 2019. 2 | 3 | import TezosKit 4 | import XCTest 5 | 6 | // TODO(keefertaylor): Merge in crypto tests. 7 | 8 | class SignedOperationPayloadTest: XCTestCase { 9 | public func testDictionaryRepresentation() { 10 | let dictionaryRepresentation = SignedOperationPayload.testSignedOperationPayload.dictionaryRepresentation 11 | XCTAssertEqual( 12 | dictionaryRepresentation["signature"] as? String, 13 | CryptoUtils.base58(signature: .testSignature, signingCurve: .ed25519) 14 | ) 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /Examples/SecureEnclave/BackgroundHighlightedButton.swift: -------------------------------------------------------------------------------- 1 | // Copyright Keefer Taylor, 2020. 2 | 3 | import UIKit 4 | 5 | class BackgroundHighlightedButton: UIButton { 6 | @IBInspectable var highlightedBackgroundColor: UIColor? 7 | @IBInspectable var nonHighlightedBackgroundColor: UIColor? 8 | override var isHighlighted: Bool { 9 | get { 10 | return super.isHighlighted 11 | } 12 | set { 13 | if newValue { 14 | self.backgroundColor = .blue 15 | } else { 16 | self.backgroundColor = .gray 17 | } 18 | super.isHighlighted = isHighlighted 19 | } 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /TezosKit/Common/Michelson/SomeMichelsonParameter.swift: -------------------------------------------------------------------------------- 1 | // Copyright Keefer Taylor, 2019. 2 | 3 | import Foundation 4 | 5 | // A representation of a some parameter in Michelson. 6 | public class SomeMichelsonParameter: AbstractMichelsonParameter { 7 | public init(some: MichelsonParameter, annotations: [MichelsonAnnotation]? = nil) { 8 | super.init( 9 | networkRepresentation: [ 10 | MichelineConstants.primitive: MichelineConstants.some, 11 | MichelineConstants.args: [ 12 | some.networkRepresentation 13 | ] 14 | ], 15 | annotations: annotations 16 | ) 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /TezosKit/TezosNode/RPC/ResponseAdapters/AbstractResponseAdapter.swift: -------------------------------------------------------------------------------- 1 | // Copyright Keefer Taylor, 2018 2 | 3 | import Foundation 4 | 5 | /// An abstract super class for all response adapters. This class is a shim to allow generics and associated types to 6 | /// play nicely together. 7 | public class AbstractResponseAdapter: ResponseAdapter { 8 | public class func parse(input _: Data) -> T? { 9 | fatalError("Use a concrete implementation of the response adapter class") 10 | } 11 | 12 | /// Please do not instantiate adapters. Adapters should only be used as static utility classes. 13 | private init() {} 14 | } 15 | -------------------------------------------------------------------------------- /Tests/UnitTests/TezosKit/MichelsonAnnotationTests.swift: -------------------------------------------------------------------------------- 1 | // Copyright Keefer Taylor, 2019. 2 | 3 | import TezosKit 4 | import XCTest 5 | 6 | final class MichelsonAnnotationTests: XCTestCase { 7 | func testValidAnnotations() { 8 | let annotationValue = "tezoskit" 9 | let validAnnotations = [ "@\(annotationValue)", "%\(annotationValue)", ":\(annotationValue)"] 10 | for annotation in validAnnotations { 11 | XCTAssertNotNil(MichelsonAnnotation(annotation: annotation)) 12 | } 13 | } 14 | 15 | func testInvalidAnnotation() { 16 | XCTAssertNil(MichelsonAnnotation(annotation: "&nonsense")) 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /TezosKit/Common/Michelson/LeftMichelsonParameter.swift: -------------------------------------------------------------------------------- 1 | // Copyright Keefer Taylor, 2019. 2 | 3 | import Foundation 4 | 5 | /// A representation of a left parameter in Michelson. 6 | public class LeftMichelsonParameter: AbstractMichelsonParameter { 7 | public init(arg: MichelsonParameter, annotations: [MichelsonAnnotation]? = nil) { 8 | let argArray = [ arg.networkRepresentation ] 9 | super.init( 10 | networkRepresentation: [ 11 | MichelineConstants.primitive: MichelineConstants.left, 12 | MichelineConstants.args: argArray 13 | ], 14 | annotations: annotations 15 | ) 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /TezosKit/Common/Michelson/RawMichelineMichelsonParameter.swift: -------------------------------------------------------------------------------- 1 | // 2 | // RawMichelineMichelsonParameter.swift 3 | // TezosKit 4 | // 5 | // Created by Simon Mcloughlin on 04/03/2020. 6 | // 7 | 8 | import Foundation 9 | 10 | /// Allows passing in Raw Micheline as a dictionary to handle any unsupported types or structures 11 | public class RawMichelineMichelsonParameter: AbstractMichelsonParameter { 12 | public init(micheline: [String: Any]) { 13 | super.init(networkRepresentation: micheline, annotations: nil) 14 | } 15 | 16 | public init(micheline: [Any]) { 17 | super.init(networkRepresentation: micheline) 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /TezosKit/Common/Michelson/RightMichelsonParameter.swift: -------------------------------------------------------------------------------- 1 | // Copyright Keefer Taylor, 2019. 2 | 3 | import Foundation 4 | 5 | // A representation of a right parameter in Michelson. 6 | public class RightMichelsonParameter: AbstractMichelsonParameter { 7 | public init(arg: MichelsonParameter, annotations: [MichelsonAnnotation]? = nil) { 8 | let argArray = [ arg.networkRepresentation ] 9 | super.init( 10 | networkRepresentation: [ 11 | MichelineConstants.primitive: MichelineConstants.right, 12 | MichelineConstants.args: argArray 13 | ], 14 | annotations: annotations 15 | ) 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /TezosKit/TezosNode/Models/OperationFees.swift: -------------------------------------------------------------------------------- 1 | // Copyright Keefer Taylor, 2018 2 | 3 | import Foundation 4 | 5 | /// An object encapsulating the payment for an operation on the blockchain. 6 | public struct OperationFees { 7 | public let fee: Tez 8 | public let gasLimit: Int 9 | public let storageLimit: Int 10 | 11 | /// A zero-ed fees object. 12 | internal static let zeroFees = OperationFees(fee: .zeroBalance, gasLimit: 0, storageLimit: 0) 13 | 14 | public init(fee: Tez, gasLimit: Int, storageLimit: Int) { 15 | self.fee = fee 16 | self.gasLimit = gasLimit 17 | self.storageLimit = storageLimit 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /TezosKit/TezosNode/RPC/InjectOperationRPC.swift: -------------------------------------------------------------------------------- 1 | // Copyright Keefer Taylor, 2018 2 | 3 | import Foundation 4 | 5 | /// An RPC which will inject an operation on the Tezos blockchain. 6 | public class InjectionRPC: RPC { 7 | /// - Parameter payload: A JSON encoded string that represents the signed payload to inject. 8 | public init( 9 | payload: String 10 | ) { 11 | let endpoint = "/injection/operation" 12 | super.init( 13 | endpoint: endpoint, 14 | headers: [Header.contentTypeApplicationJSON], 15 | responseAdapterClass: StringResponseAdapter.self, 16 | payload: payload 17 | ) 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /TezosKit/TezosNode/RPC/PackDataRPC.swift: -------------------------------------------------------------------------------- 1 | // Copyright Keefer Taylor, 2020 2 | 3 | import Foundation 4 | 5 | /// An RPC which will pack data to a binary format. 6 | public class PackDataRPC: RPC { 7 | public init( 8 | payload: PackDataPayload 9 | ) { 10 | let endpoint = "/chains/main/blocks/head/helpers/scripts/pack_data" 11 | let payload = JSONUtils.jsonString(for: payload.dictionaryRepresentation) 12 | 13 | super.init( 14 | endpoint: endpoint, 15 | headers: [ Header.contentTypeApplicationJSON ], 16 | responseAdapterClass: PackDataResponseAdapter.self, 17 | payload: payload 18 | ) 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /TezosKit/TezosNode/RPC/ResponseAdapters/JSONDictionaryResponseAdapter.swift: -------------------------------------------------------------------------------- 1 | // Copyright Keefer Taylor, 2018 2 | 3 | import Foundation 4 | 5 | /// Parse the given data as a JSON encoded string representing a dictionary. 6 | public class JSONDictionaryResponseAdapter: AbstractResponseAdapter<[String: Any]> { 7 | public override class func parse(input: Data) -> [String: Any]? { 8 | do { 9 | let json = try JSONSerialization.jsonObject(with: input) 10 | guard let typedJSON = json as? [String: Any] else { 11 | return nil 12 | } 13 | 14 | return typedJSON 15 | } catch { 16 | return nil 17 | } 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /Tests/UnitTests/TezosKit/SigningServiceTests.swift: -------------------------------------------------------------------------------- 1 | // Copyright Keefer Taylor, 2019. 2 | 3 | import TezosKit 4 | import XCTest 5 | 6 | class SigningServiceTests: XCTestCase { 7 | func testSign() { 8 | let mockTransaction = "abc123" 9 | let expectedSignature: [UInt8] = [1, 2, 3, 4] 10 | 11 | let fakeSignatureProvider = 12 | FakeSignatureProvider(signature: expectedSignature, publicKey: FakePublicKey.testPublicKey) 13 | 14 | guard let signature = SigningService.sign(mockTransaction, with: fakeSignatureProvider) else { 15 | XCTFail() 16 | return 17 | } 18 | XCTAssertEqual(signature, expectedSignature) 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /TezosKit/Common/Michelson/AddressMichelsonParameter.swift: -------------------------------------------------------------------------------- 1 | // Copyright Keefer Taylor, 2020. 2 | 3 | import Foundation 4 | 5 | /// A representation of a address parameter in Michelson. 6 | public class AddressMichelsonParameter: AbstractMichelsonParameter { 7 | /// Initialize a new address parameter. 8 | /// 9 | /// - Parameters: 10 | /// - address: An address. 11 | /// - annotations: An optional array of annotations to apply, default is none. 12 | public init(address: String, annotations: [MichelsonAnnotation]? = nil) { 13 | super.init(networkRepresentation: [MichelineConstants.string: address], annotations: annotations) 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /TezosKit/Common/Michelson/MichelsonComparable.swift: -------------------------------------------------------------------------------- 1 | // Copyright Keefer Taylor, 2019. 2 | 3 | import Foundation 4 | 5 | /// A comparable Michelson type. 6 | public enum MichelsonComparable: String { 7 | case address 8 | case bool 9 | case bytes 10 | case int 11 | case keyHash = "key_hash" 12 | case mutez 13 | case nat 14 | case string 15 | case timestamp 16 | 17 | private enum JSON { 18 | public enum Keys { 19 | public static let prim = "prim" 20 | } 21 | } 22 | 23 | public static func networkRepresentation(for type: MichelsonComparable) -> [String: String] { 24 | return [ JSON.Keys.prim: type.rawValue ] 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /TezosKit/Common/TezosKitError.swift: -------------------------------------------------------------------------------- 1 | // Copyright Keefer Taylor, 2018 2 | 3 | import Foundation 4 | 5 | public indirect enum TezosKitError: Error, Equatable { 6 | case internalError 7 | case invalidURL 8 | case localForgingNotSupportedForOperation 9 | case operationError([OperationResponseInternalResultError]) 10 | case preapplicationError(description: String) 11 | case rpcError(description: String) 12 | case signingError 13 | case transactionFormationFailure(underlyingError: TezosKitError) 14 | case unexpectedRequestFormat(description: String) 15 | case unexpectedResponse(description: String) 16 | case unknown(description: String?) 17 | } 18 | -------------------------------------------------------------------------------- /TezosKit/TezosNode/RPC/ResponseAdapters/JSONArrayResponseAdapter.swift: -------------------------------------------------------------------------------- 1 | // Copyright Keefer Taylor, 2018 2 | 3 | import Foundation 4 | 5 | /// Parse the given data as a JSON encoded string representing an array of nested dictionaries. 6 | public class JSONArrayResponseAdapter: AbstractResponseAdapter<[[String: Any]]> { 7 | public override class func parse(input: Data) -> [[String: Any]]? { 8 | do { 9 | let json = try JSONSerialization.jsonObject(with: input) 10 | guard let typedJSON = json as? [[String: Any]] else { 11 | return nil 12 | } 13 | return typedJSON 14 | } catch { 15 | return nil 16 | } 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /TezosKit/Common/Michelson/TimestampMichelsonParameter.swift: -------------------------------------------------------------------------------- 1 | // Copyright Keefer Taylor, 2020. 2 | 3 | import Foundation 4 | 5 | /// A representation of a date parameter in Michelson. 6 | public class Timestamp: AbstractMichelsonParameter { 7 | public init(date: Date, annotations: [MichelsonAnnotation]? = nil) { 8 | let dateFormatter = DateFormatter() 9 | dateFormatter.dateFormat = "yyyy-MM-dd'T'HH:mm:ss'Z'" 10 | dateFormatter.timeZone = TimeZone(abbreviation: "GMT") 11 | 12 | let string = dateFormatter.string(from: date) 13 | 14 | super.init(networkRepresentation: [MichelineConstants.string: string], annotations: annotations) 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /TezosKit/Common/Michelson/PairMichelsonParameter.swift: -------------------------------------------------------------------------------- 1 | // Copyright Keefer Taylor, 2019 2 | 3 | import Foundation 4 | 5 | // A representation of a pair parameter in Michelson. 6 | public class PairMichelsonParameter: AbstractMichelsonParameter { 7 | public init(left: MichelsonParameter, right: MichelsonParameter, annotations: [MichelsonAnnotation]? = nil) { 8 | let argArray = [ left.networkRepresentation, right.networkRepresentation ] 9 | super.init( 10 | networkRepresentation: [ 11 | MichelineConstants.primitive: MichelineConstants.pair, 12 | MichelineConstants.args: argArray 13 | ], 14 | annotations: annotations 15 | ) 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /TezosKit/Common/Michelson/BytesMichelsonParameter.swift: -------------------------------------------------------------------------------- 1 | // Copyright Keefer Taylor, 2019. 2 | 3 | import Foundation 4 | 5 | /// A representation of a bytes parameter in Michelson. 6 | public class BytesMichelsonParameter: AbstractMichelsonParameter { 7 | public convenience init?(bytes: [UInt8], annotations: [MichelsonAnnotation]? = nil) { 8 | guard let hex = CryptoUtils.binToHex(bytes) else { 9 | return nil 10 | } 11 | self.init(hex: hex, annotations: annotations) 12 | } 13 | 14 | public init(hex: Hex, annotations: [MichelsonAnnotation]? = nil) { 15 | super.init(networkRepresentation: [ MichelineConstants.bytes: hex ], annotations: annotations) 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /TezosKit/Common/Michelson/SignatureMichelsonParameter.swift: -------------------------------------------------------------------------------- 1 | // Copyright Keefer Taylor, 2020. 2 | 3 | import Foundation 4 | 5 | /// A representation of a signature parameter in Michelson. 6 | public class SignatureMichelsonParameter: AbstractMichelsonParameter { 7 | /// Initialize a new address parameter. 8 | /// 9 | /// - Parameters: 10 | /// - signature: A base58check encoded signature. 11 | /// - annotations: An optional array of annotations to apply, default is none. 12 | public init(signature: String, annotations: [MichelsonAnnotation]? = nil) { 13 | super.init(networkRepresentation: [MichelineConstants.string: signature], annotations: annotations) 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /TezosKit/Common/Models/KeyChainWallet.swift: -------------------------------------------------------------------------------- 1 | // Copyright Keefer Taylor, 2020. 2 | 3 | import Foundation 4 | 5 | /// A wallet which stores keys in a device's keychain. 6 | public class KeyChainWallet: DeviceWallet { 7 | /// Labels for keys in the Keychain. 8 | private enum KeyLabels { 9 | public static let `public` = "TezosKitKeyChain.public" 10 | public static let `private` = "TezosKitKeyChain.private" 11 | } 12 | 13 | /// - Parameter prompt: A prompt to use when asking the wallet to sign bytes. 14 | public init?(prompt: String) { 15 | super.init(prompt: prompt, token: .keychain, publicKeyLabel: KeyLabels.public, privateKeyLabel: KeyLabels.private) 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /Tests/UnitTests/TezosKit/RunOperationRPCTest.swift: -------------------------------------------------------------------------------- 1 | // Copyright Keefer Taylor, 2019 2 | 3 | import XCTest 4 | 5 | @testable import TezosKit 6 | 7 | class RunOperationRPCTest: XCTestCase { 8 | public func testForgeOperationRPC() { 9 | let rpc = RunOperationRPC(runOperationPayload: .testRunOperationPayload) 10 | 11 | let expectedEndpoint = 12 | "/chains/main/blocks/head/helpers/scripts/run_operation" 13 | let expectedPayload = 14 | JSONUtils.jsonString(for: RunOperationPayload.testRunOperationPayload.dictionaryRepresentation) 15 | 16 | XCTAssertEqual(rpc.endpoint, expectedEndpoint) 17 | XCTAssertEqual(rpc.payload, expectedPayload) 18 | XCTAssertTrue(rpc.isPOSTRequest) 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /Tests/UnitTests/TezosKit/GetAddressManagerKeyRPCTest.swift: -------------------------------------------------------------------------------- 1 | // Copyright Keefer Taylor, 2018 2 | 3 | import TezosKit 4 | // 5 | // GetAddressManagerKeyRPCTest.swift 6 | // TezosKitTests 7 | // 8 | // Created by Keefer Taylor on 10/26/18. 9 | // Copyright © 2018 Keefer Taylor. All rights reserved. 10 | // 11 | import XCTest 12 | 13 | class GetAddressManagerKeyRPCTest: XCTestCase { 14 | public func testGetAddressManagerKeyRPC() { 15 | let address = "abc123" 16 | let rpc = GetAddressManagerKeyRPC(address: address) 17 | 18 | XCTAssertEqual(rpc.endpoint, "/chains/main/blocks/head/context/contracts/" + address + "/manager_key") 19 | XCTAssertNil(rpc.payload) 20 | XCTAssertFalse(rpc.isPOSTRequest) 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /TezosKit/Common/Michelson/ChainIDMichelsonParameter.swift: -------------------------------------------------------------------------------- 1 | // Copyright Keefer Taylor, 2019. 2 | 3 | import Foundation 4 | 5 | /// A representation of a chain ID parameter in Michelson. 6 | public class ChainIDMichelsonParameter: AbstractMichelsonParameter { 7 | public convenience init?(chainIDBytes: [UInt8], annotations: [MichelsonAnnotation]? = nil) { 8 | guard let hex = CryptoUtils.binToHex(chainIDBytes) else { 9 | return nil 10 | } 11 | self.init(chainIDHex: hex, annotations: annotations) 12 | } 13 | 14 | public init(chainIDHex: Hex, annotations: [MichelsonAnnotation]? = nil) { 15 | super.init(networkRepresentation: [ MichelineConstants.bytes: chainIDHex ], annotations: annotations) 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /TezosKit/Common/Michelson/KeyHashMichelsonParameter.swift: -------------------------------------------------------------------------------- 1 | // Copyright Keefer Taylor, 2019. 2 | 3 | import Foundation 4 | 5 | /// A representation of a key hash parameter in Michelson. 6 | public class KeyHashMichelsonParameter: AbstractMichelsonParameter { 7 | public convenience init?(keyHashBytes: [UInt8], annotations: [MichelsonAnnotation]? = nil) { 8 | guard let hex = CryptoUtils.binToHex(keyHashBytes) else { 9 | return nil 10 | } 11 | self.init(keyHashHex: hex, annotations: annotations) 12 | } 13 | 14 | public init(keyHashHex: Hex, annotations: [MichelsonAnnotation]? = nil) { 15 | super.init(networkRepresentation: [ MichelineConstants.bytes: keyHashHex ], annotations: annotations) 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /TezosKit/TezosNode/RPC/RunOperationRPC.swift: -------------------------------------------------------------------------------- 1 | // Copyright Keefer Taylor, 2019. 2 | 3 | import Foundation 4 | 5 | /// An RPC that will run an operation. 6 | public class RunOperationRPC: RPC { 7 | /// - Parameter runOperationPayload: A payload containing an operation to run. 8 | public init(runOperationPayload: RunOperationPayload) { 9 | let endpoint = "/chains/main/blocks/head/helpers/scripts/run_operation" 10 | let jsonPayload = JSONUtils.jsonString(for: runOperationPayload.dictionaryRepresentation) 11 | super.init( 12 | endpoint: endpoint, 13 | headers: [Header.contentTypeApplicationJSON], 14 | responseAdapterClass: SimulationResultResponseAdapter.self, 15 | payload: jsonPayload 16 | ) 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /TezosKit/TezosNode/Models/Operation/Operation.swift: -------------------------------------------------------------------------------- 1 | // Copyright Keefer Taylor, 2018 2 | 3 | import Foundation 4 | 5 | /// Protocol representing all operations. Operations are first class representations of JSON object which can be forged 6 | /// pre-applied / injected on the Tezos Blockchain. 7 | public protocol Operation: NSMutableCopying { 8 | /// The kind of the operation. 9 | var kind: OperationKind { get } 10 | 11 | /// Whether the given operation requires the account to be revealed. 12 | var requiresReveal: Bool { get } 13 | 14 | /// Fees associated with the operation. 15 | var operationFees: OperationFees { get set } 16 | 17 | /// Retrieve a dictionary representing the operation. 18 | var dictionaryRepresentation: [String: Any] { get } 19 | } 20 | -------------------------------------------------------------------------------- /TezosKit/TezosNode/RPC/GetBigMapValueByIDRPC.swift: -------------------------------------------------------------------------------- 1 | // Copyright Keefer Taylor, 2019 2 | 3 | import BigInt 4 | import Foundation 5 | 6 | /// An RPC that will retrieve the value of a big map for the given key. 7 | public class GetBigMapValueByIDRPC: RPC<[String: Any]> { 8 | /// - Parameters: 9 | /// - address: The address of a smart contract with a big map. 10 | /// - key: The key in the big map to look up. 11 | /// - type: The michelson type of the key. 12 | public init(bigMapID: BigInt, expression: String) { 13 | let endpoint = "chains/main/blocks/head/context/big_maps/" + String(bigMapID) + "/" + expression 14 | 15 | super.init( 16 | endpoint: endpoint, 17 | responseAdapterClass: JSONDictionaryResponseAdapter.self 18 | ) 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /Tests/UnitTests/TezosKit/ForgeOperationRPCTest.swift: -------------------------------------------------------------------------------- 1 | // Copyright Keefer Taylor, 2018 2 | 3 | @testable import TezosKit 4 | import XCTest 5 | 6 | class ForgeOperationRPCTest: XCTestCase { 7 | public func testForgeOperationRPC() { 8 | let rpc = ForgeOperationRPC(operationPayload: .testOperationPayload, operationMetadata: .testOperationMetadata) 9 | 10 | let expectedEndpoint = 11 | "/chains/main/blocks/" + OperationMetadata.testOperationMetadata.branch + "/helpers/forge/operations" 12 | let expectedPayload = JSONUtils.jsonString(for: OperationPayload.testOperationPayload.dictionaryRepresentation) 13 | 14 | XCTAssertEqual(rpc.endpoint, expectedEndpoint) 15 | XCTAssertEqual(rpc.payload, expectedPayload) 16 | XCTAssertTrue(rpc.isPOSTRequest) 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /TezosKit/TezosNode/Models/Operation/OperationWithCounter.swift: -------------------------------------------------------------------------------- 1 | // Copyright Keefer Taylor, 2019. 2 | 3 | import Foundation 4 | 5 | /// An operation which wraps an operation with an address counter for that operation. 6 | public struct OperationWithCounter { 7 | /// The internal operation. 8 | internal let operation: Operation 9 | 10 | /// The address counter for the internal operation. 11 | internal let counter: Int 12 | 13 | public var dictionaryRepresentation: [String: Any] { 14 | var operationDict = operation.dictionaryRepresentation 15 | operationDict["counter"] = String(counter) 16 | return operationDict 17 | } 18 | 19 | public init(operation: Operation, counter: Int) { 20 | self.operation = operation 21 | self.counter = counter 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /Tests/UnitTests/TezosKit/PackDataResponseAdapterTest.swift: -------------------------------------------------------------------------------- 1 | // Copyright Keefer Taylor, 2020 2 | 3 | import TezosKit 4 | import XCTest 5 | 6 | class PackDataResponseAdapterTest: XCTestCase { 7 | public func testParsePackedData() { 8 | let expected = "exprv6UsC1sN3Fk2XfgcJCL8NCerP5rCGy1PRESZAqr7L2JdzX55EN" 9 | let response = [ 10 | "packed": "050a000000160000b2e19a9e74440d86c59f13dab8a18ff873e889ea", 11 | "gas": "799901" 12 | ] 13 | 14 | guard 15 | let jsonResponse = JSONUtils.jsonString(for: response), 16 | let data = jsonResponse.data(using: .utf8) 17 | else { 18 | XCTFail() 19 | return 20 | } 21 | 22 | let result = PackDataResponseAdapter.parse(input: data) 23 | 24 | XCTAssertEqual(result, expected) 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /Extensions/PromiseKit/Common/NetworkClient+Promises.swift: -------------------------------------------------------------------------------- 1 | // Copyright Keefer Taylor, 2019 2 | 3 | import Foundation 4 | import PromiseKit 5 | 6 | /// Extension of NetworkClient which provides Promise based functionality. 7 | extension NetworkClient { 8 | /// Send an RPC and return the result as a promise. 9 | /// - Parameters: 10 | /// - rpc: The RPC to send. 11 | /// - Returns: A promise which will resolve to the result of the RPC. 12 | public func send(_ rpc: RPC) -> Promise { 13 | return Promise { seal in 14 | send(rpc) { result in 15 | switch result { 16 | case .success(let data): 17 | seal.fulfill(data) 18 | case .failure(let error): 19 | seal.reject(error) 20 | } 21 | } 22 | } 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /Tests/UnitTests/TezosKit/GetBigMapValueRPCTest.swift: -------------------------------------------------------------------------------- 1 | // Copyright Keefer Taylor, 2019. 2 | 3 | import TezosKit 4 | import XCTest 5 | 6 | final class GetBigMapValueRPCTest: XCTestCase { 7 | func testGetBigMapValueRPC() { 8 | let michelsonAddress = StringMichelsonParameter(string: .testAddress) 9 | let rpc = GetBigMapValueRPC(address: .testAddress, key: michelsonAddress, type: .address) 10 | 11 | let expectedEndpoint = "/chains/main/blocks/head/context/contracts/\(String.testAddress)/big_map_get" 12 | let expectedPayload = "{\"key\":{\"string\":\"\(String.testAddress)\"},\"type\":{\"prim\":\"address\"}}" 13 | 14 | XCTAssertEqual(rpc.endpoint, expectedEndpoint) 15 | XCTAssertEqual(rpc.payload, Helpers.orderJSONString(expectedPayload)) 16 | XCTAssertTrue(rpc.isPOSTRequest) 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /Tests/UnitTests/TezosKit/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | BNDL 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleVersion 20 | 1 21 | 22 | 23 | -------------------------------------------------------------------------------- /Tests/IntegrationTests/TezosKit/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | BNDL 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleVersion 20 | 1 21 | 22 | 23 | -------------------------------------------------------------------------------- /TezosKit/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | FMWK 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleVersion 20 | $(CURRENT_PROJECT_VERSION) 21 | 22 | 23 | -------------------------------------------------------------------------------- /TezosKit/TezosNode/Models/OperationMetadata.swift: -------------------------------------------------------------------------------- 1 | // Copyright Keefer Taylor, 2018 2 | 3 | import Foundation 4 | 5 | /// A lightweight property bag containing data about the current chain state in order to forge / preapply / inject 6 | /// operations on the Tezos blockchain. 7 | public struct OperationMetadata { 8 | /// The ID of the chain being operated on. 9 | public let chainID: String 10 | 11 | /// The hash of the head of the chain being operated on. 12 | public let branch: String 13 | 14 | /// The hash of the protocol being operated on. 15 | public let `protocol`: String 16 | 17 | /// The counter for the address being operated on. 18 | public let addressCounter: Int 19 | 20 | /// The base58encoded public key for the address, or nil if the key is not yet revealed. 21 | public let key: String? 22 | } 23 | -------------------------------------------------------------------------------- /TezosKit/TezosNode/RPC/ForgeOperationRPC.swift: -------------------------------------------------------------------------------- 1 | // Copyright Keefer Taylor, 2018 2 | 3 | import Foundation 4 | 5 | /// An RPC which will forge an operation. 6 | public class ForgeOperationRPC: RPC { 7 | /// - Parameters: 8 | /// - operationPayload: A payload to forge. 9 | /// - operationMetadata: Metadata about the operation. 10 | public init( 11 | operationPayload: OperationPayload, 12 | operationMetadata: OperationMetadata 13 | ) { 14 | let endpoint = 15 | "/chains/main/blocks/" + operationMetadata.branch + "/helpers/forge/operations" 16 | let payload = JSONUtils.jsonString(for: operationPayload.dictionaryRepresentation) 17 | super.init( 18 | endpoint: endpoint, 19 | headers: [Header.contentTypeApplicationJSON], 20 | responseAdapterClass: StringResponseAdapter.self, 21 | payload: payload 22 | ) 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /Examples/TezosKitExample.playground/Contents.swift: -------------------------------------------------------------------------------- 1 | // Copyright Keefer Taylor, 2018 2 | 3 | import TezosKit 4 | 5 | let tezosNodeClient = TezosNodeClient() 6 | let wallet = Wallet()! 7 | 8 | tezosNodeClient.getHeadHash { headHash, error in 9 | guard let headHash = headHash, 10 | error == nil else { 11 | print("Couldn't fetch head :(") 12 | if let error = error { 13 | print("Error: \(error)") 14 | } 15 | return 16 | } 17 | print("The hash of the head block was \(headHash)") 18 | } 19 | 20 | tezosNodeClient.getBalance(wallet: wallet) { balance, error in 21 | guard let balance = balance, 22 | error == nil else { 23 | print("Couldn't fetch balance :(") 24 | if let error = error { 25 | print("Error: \(error)") 26 | } 27 | return 28 | } 29 | print("The balance of \(wallet.address) was \(balance.humanReadableRepresentation)") 30 | } 31 | -------------------------------------------------------------------------------- /Tests/UnitTests/TezosKit/PreapplyOperationRPCTest.swift: -------------------------------------------------------------------------------- 1 | // Copyright Keefer Taylor, 2018 2 | 3 | import TezosKit 4 | import XCTest 5 | 6 | final class PreapplyOperationRPCTest: XCTestCase { 7 | func testPreapplyOperationRPC() { 8 | let rpc = PreapplyOperationRPC( 9 | signedProtocolOperationPayload: .testSignedProtocolOperationPayload, 10 | operationMetadata: .testOperationMetadata 11 | ) 12 | 13 | let expectedEndpoint = 14 | "/chains/main/blocks/" + OperationMetadata.testOperationMetadata.branch + "/helpers/preapply/operations" 15 | let expectedPayloadDictionary = 16 | SignedProtocolOperationPayload.testSignedProtocolOperationPayload.dictionaryRepresentation 17 | let expectedPayload = JSONUtils.jsonString(for: expectedPayloadDictionary) 18 | 19 | XCTAssertEqual(rpc.endpoint, expectedEndpoint) 20 | XCTAssertEqual(rpc.payload, expectedPayload) 21 | XCTAssertTrue(rpc.isPOSTRequest) 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /Tests/Common/TestHelpers.swift: -------------------------------------------------------------------------------- 1 | // Copyright Keefer Taylor, 2019. 2 | 3 | import Foundation 4 | import TezosKit 5 | 6 | // swiftlint:disable force_cast 7 | // swiftlint:disable force_try 8 | 9 | /// Common helpers for all TezosKit unit tests. 10 | public enum Helpers { 11 | /// Fix the expected input to the expected output of Swift's JSON serializer. 12 | /// 13 | /// Expected output is taken from the human readable version of the JSON serialization format. Swift outputs JSON 14 | /// keys either (1) non-deterministically or (2) ordered by key. This function re-orders the expected outputs of a 15 | /// input JSON string by key so that asserts can work properly. 16 | public static func orderJSONString(_ expected: String) -> String { 17 | let data = expected.data(using: .utf8)! 18 | let dictionary = try! JSONSerialization.jsonObject(with: data, options: []) as! [String: Any] 19 | return JSONUtils.jsonString(for: dictionary)! 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /TezosKit/Conseil/RPC/ConseilQueryRPC.swift: -------------------------------------------------------------------------------- 1 | // Copyright Keefer Taylor, 2019 2 | 3 | import Foundation 4 | 5 | /// Generic RPC to query Conseil. 6 | public class ConseilQueryRPC: RPC { 7 | /// - Parameters: 8 | /// - query: A query to send to Conseil 9 | /// - entity: The entity to query. 10 | /// - responseAdapterClass: The class of the response adapter which will take bytes received from the request and 11 | /// transform them into a specific type. 12 | public init( 13 | query: [String: Any], 14 | entity: ConseilEntity, 15 | responseAdapterClass: AbstractResponseAdapter.Type 16 | ) { 17 | let endpoint = "\(entity.rawValue)" 18 | let headers = [ 19 | Header.contentTypeApplicationJSON 20 | ] 21 | 22 | super.init( 23 | endpoint: endpoint, 24 | headers: headers, 25 | responseAdapterClass: responseAdapterClass, 26 | payload: JSONUtils.jsonString(for: query) 27 | ) 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /docs/README.md: -------------------------------------------------------------------------------- 1 | # TezosKit Documentation 2 | 3 | ## Contents 4 | 5 | ### Getting Started 6 | - [Working with the Tezos Node](TezosNode.md): Instructions to work with a TezosNode 7 | - [Working with Conseil Service](Conseil.md): Instructions to work with Conseil 8 | - [Planned Work](FutureWork.md): Planned work for the future 9 | - [Testing](Testing.md): TezosKit unit and integration testing philosophy and instructions 10 | - [Advanced Functionality](AdvancedFunctionality.md): Instructions to use advanced Functions in TezosKit 11 | 12 | ### Advanced Topics 13 | - [Michelson](Michelson.md): Information about working with Michelson in TezosKit. 14 | - [Fees](Fees.md): Information about how TezosKit assigns fees 15 | - [Networking](Networking.md): Information about Networking in TezosKit 16 | - [Operations](Operations.md): Information about Operations in TezosKit 17 | - [Promises / PromiseKit](PromiseKit.md): About TezosKit's [PromiseKit](https://github.com/mxcl/PromiseKit) integration 18 | -------------------------------------------------------------------------------- /Tests/UnitTests/TezosKit/RevealOperationTest.swift: -------------------------------------------------------------------------------- 1 | // Copyright Keefer Taylor, 2018 2 | 3 | import TezosKit 4 | import XCTest 5 | 6 | class RevealOperationTest: XCTestCase { 7 | public func testDictionaryRepresentation() { 8 | let publicKey = FakePublicKey.testPublicKey 9 | 10 | guard case let .success(operation) = OperationFactory.testFactory.revealOperation( 11 | from: publicKey.publicKeyHash, 12 | publicKey: publicKey, 13 | operationFeePolicy: .default, 14 | signatureProvider: FakeSignatureProvider.testSignatureProvider 15 | ) else { 16 | XCTFail() 17 | return 18 | } 19 | let dictionary = operation.dictionaryRepresentation 20 | 21 | XCTAssertNotNil(dictionary["source"]) 22 | XCTAssertEqual(dictionary["source"] as? String, publicKey.publicKeyHash) 23 | 24 | XCTAssertNotNil(dictionary["public_key"]) 25 | XCTAssertEqual(dictionary["public_key"] as? String, publicKey.base58CheckRepresentation) 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /TezosKit/TezosNode/RPC/PreapplyOperationRPC.swift: -------------------------------------------------------------------------------- 1 | // Copyright Keefer Taylor, 2018 2 | 3 | import Foundation 4 | 5 | /// An RPC which will pre-apply an operation. 6 | public class PreapplyOperationRPC: RPC<[[String: Any]]> { 7 | /// - Parameters: 8 | /// - signedProtocolOperationPayload: A payload to send with the operation. 9 | /// - operationMetadata: Metadata about the operation to be pre-applied. 10 | public init( 11 | signedProtocolOperationPayload: SignedProtocolOperationPayload, 12 | operationMetadata: OperationMetadata 13 | ) { 14 | let endpoint = "/chains/main/blocks/" + operationMetadata.branch + "/helpers/preapply/operations" 15 | let payload = JSONUtils.jsonString(for: signedProtocolOperationPayload.dictionaryRepresentation) 16 | super.init( 17 | endpoint: endpoint, 18 | headers: [Header.contentTypeApplicationJSON], 19 | responseAdapterClass: JSONArrayResponseAdapter.self, 20 | payload: payload 21 | ) 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /TezosKit/TezosNode/RPC/ResponseAdapters/StringResponseAdapter.swift: -------------------------------------------------------------------------------- 1 | // Copyright Keefer Taylor, 2018 2 | 3 | import Foundation 4 | 5 | /// Parse the given data as a string. 6 | /// 7 | /// Note that the API returns strings enclosed inside of whitespace, newlines and double quotes. 8 | /// These characters are stripped by this adapter. 9 | public class StringResponseAdapter: AbstractResponseAdapter { 10 | public override class func parse(input: Data) -> String? { 11 | guard 12 | let decodedString = String(data: input, encoding: .utf8) 13 | else { 14 | return nil 15 | } 16 | 17 | let characterSet = CharacterSet(charactersIn: "\"").union(.whitespacesAndNewlines) 18 | let normalizedString = decodedString.trimmingCharacters(in: characterSet) 19 | // RPC API will just pass through `null` when response is not found. 20 | guard normalizedString != "null" else { 21 | return nil 22 | } 23 | 24 | return normalizedString 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /TezosKit/Conseil/RPC/GetReceivedTransactions.RPC.swift: -------------------------------------------------------------------------------- 1 | // Copyright Keefer Taylor, 2019. 2 | 3 | /// An RPC which fetches received transactions for an account. 4 | public class GetReceivedTransactionsRPC: ConseilQueryRPC<[Transaction]> { 5 | /// - Parameters: 6 | /// - account: The account to query. 7 | /// - limit: The number of items to return. 8 | public init(account: String, limit: Int) { 9 | let predicates: [ConseilPredicate] = [ 10 | ConseilQuery.Predicates.predicateWith(field: "kind", set: ["transaction"]), 11 | ConseilQuery.Predicates.predicateWith(field: "destination", set: [account]) 12 | ] 13 | let orderBy: ConseilOrderBy = ConseilQuery.OrderBy.orderBy(field: "timestamp") 14 | let query: [String: Any] = ConseilQuery.query(predicates: predicates, orderBy: orderBy, limit: limit) 15 | 16 | super.init( 17 | query: query, 18 | entity: .operation, 19 | responseAdapterClass: TransactionsResponseAdapter.self 20 | ) 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /docs/AdvancedFunctionality.md: -------------------------------------------------------------------------------- 1 | # Advanced Functionality 2 | TezosKit provides advanced functionality and extensibility out of the box. 3 | 4 | ## Custom RPCs and Operations 5 | Users can create their own RPCs and Operations to support operations on the chain which TezosKit does not provide support for. 6 | 7 | See: 8 | * [RPC Documentation](Networking.md) 9 | * [Operation Documentation](Operations.md) 10 | 11 | ## Preapplication 12 | 13 | Every operation in TezosKit is pre-applied before it is injected in the block chain to catch errors. 14 | 15 | A future update of TezosKit will make this functionality optional. 16 | 17 | ## Local Operation Forging 18 | Forging of operations is currently done remotely. 19 | 20 | A future update of TezosKit will make this functionality available locally and provide a toggle to change functionality. 21 | 22 | ## Fees 23 | Users can set custom fees on operations. See the [Fees Documentation](Fees.md). 24 | 25 | A future update of TezosKit will provide gas estimation. -------------------------------------------------------------------------------- /TezosKit/TezosNode/Services/InjectionService.swift: -------------------------------------------------------------------------------- 1 | // Copyright Keefer Taylor, 2019. 2 | 3 | import Foundation 4 | 5 | public class InjectionService { 6 | /// The network client. 7 | private let networkClient: NetworkClient 8 | 9 | public init(networkClient: NetworkClient) { 10 | self.networkClient = networkClient 11 | } 12 | 13 | /// Inject the given hex into the remote node. 14 | /// 15 | /// - Parameters: 16 | /// - payload: A hex payload to inject. 17 | /// - completion: A completion block that will be called with either an operation hash or an error. 18 | public func inject(payload: Hex, completion: @escaping (Result) -> Void) { 19 | let injectRPC = InjectionRPC(payload: payload) 20 | networkClient.send(injectRPC) { result in 21 | switch result { 22 | case .failure(let txError): 23 | completion(.failure(txError)) 24 | case .success(let txHash): 25 | completion(.success(txHash)) 26 | } 27 | } 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /TezosKit/Conseil/RPC/GetSentTransactionsRPC.swift: -------------------------------------------------------------------------------- 1 | // Copyright Keefer Taylor, 2019. 2 | 3 | /// An RPC which fetches sent transactions from an account. 4 | public class GetSentTransactionsRPC: ConseilQueryRPC<[Transaction]> { 5 | /// - Parameters: 6 | /// - account: The account to query. 7 | /// - limit: The number of items to return. 8 | public init(account: String, limit: Int) { 9 | let predicates = [ 10 | ConseilQuery.Predicates.predicateWith(field: "kind", operation: .in, set: ["transaction", "delegation", "origination", "reveal", "activate_account"], inverse: false), 11 | ConseilQuery.Predicates.predicateWith(field: "source", set: [account]) 12 | ] 13 | let orderBy = ConseilQuery.OrderBy.orderBy(field: "timestamp") 14 | let query = ConseilQuery.query(predicates: predicates, orderBy: orderBy, limit: limit) 15 | 16 | super.init( 17 | query: query, 18 | entity: .operation, 19 | responseAdapterClass: TransactionsResponseAdapter.self 20 | ) 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /TezosKit/Common/Michelson/KeyMichelsonParameter.swift: -------------------------------------------------------------------------------- 1 | // Copyright Keefer Taylor, 2019. 2 | 3 | import Foundation 4 | 5 | /// A representation of a key parameter in Michelson. 6 | public class KeyMichelsonParameter: AbstractMichelsonParameter { 7 | /// Initialize a new parameter using a secret key. 8 | public convenience init(secretKey: SecretKey, annotations: [MichelsonAnnotation]? = nil) { 9 | self.init(string: secretKey.base58CheckRepresentation, annotations: annotations) 10 | } 11 | 12 | /// Initialize a new parameter using a public key. 13 | public convenience init(publicKey: PublicKeyProtocol, annotations: [MichelsonAnnotation]? = nil) { 14 | self.init(string: publicKey.base58CheckRepresentation, annotations: annotations) 15 | } 16 | 17 | /// Private initializer which receives a base58check representation of a key. 18 | private init(string: String, annotations: [MichelsonAnnotation]? = nil) { 19 | super.init(networkRepresentation: [MichelineConstants.string: string], annotations: annotations) 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /TezosKit/TezosNode/RPC/ResponseAdapters/PackDataResponseAdapter.swift: -------------------------------------------------------------------------------- 1 | // Copyright Keefer Taylor, 2020 2 | 3 | import Base58Swift 4 | import Foundation 5 | import Sodium 6 | 7 | /// Parse a give response to a Base58check encoded set of packed data. 8 | public class PackDataResponseAdapter: AbstractResponseAdapter { 9 | public override class func parse(input: Data) -> String? { 10 | guard 11 | let dictionary = JSONDictionaryResponseAdapter.parse(input: input), 12 | let packedHex = dictionary["packed"] as? String, 13 | let packedBinary = CryptoUtils.hexToBin(packedHex) 14 | else { 15 | return nil 16 | } 17 | 18 | // Prefix and Base58Check encode. 19 | // TODO(keefertaylor): Push this down into the crypto library. 20 | let hashed = Sodium.shared.genericHash.hash(message: packedBinary, outputLength: 32)! 21 | let prefix: [UInt8] = [13, 44, 64, 27] // expr 22 | let prefixedPackedBinary = prefix + hashed 23 | 24 | return Base58.base58CheckEncode(prefixedPackedBinary) 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /TezosKit/Conseil/RPC/GetOriginatedContractsRPC.swift: -------------------------------------------------------------------------------- 1 | // Copyright Keefer Taylor, 2019. 2 | 3 | /// An RPC which fetches originated contracts for an account. 4 | public class GetOriginatedContractsRPC: ConseilQueryRPC<[[String: Any]]> { 5 | /// - Parameters: 6 | /// - account: The account to query. 7 | /// - limit: The number of items to return. 8 | public init(account: String, limit: Int) { 9 | let predicates: [ConseilPredicate] = [ 10 | ConseilQuery.Predicates.predicateWith(field: "source", set: [account]), 11 | ConseilQuery.Predicates.predicateWith(field: "kind", set: ["origination"]), 12 | ConseilQuery.Predicates.predicateWith(field: "status", set: ["applied"]) 13 | ] 14 | let orderBy: ConseilOrderBy = ConseilQuery.OrderBy.orderBy(field: "block_level") 15 | let query: [String: Any] = ConseilQuery.query(predicates: predicates, orderBy: orderBy, limit: limit) 16 | 17 | super.init( 18 | query: query, 19 | entity: .operation, 20 | responseAdapterClass: JSONArrayResponseAdapter.self 21 | ) 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /TezosKit/Common/Michelson/MichelsonParameter.swift: -------------------------------------------------------------------------------- 1 | // Copyright Keefer Taylor, 2019. 2 | 3 | import Foundation 4 | 5 | /// String constants used in Micheline param JSON encoding. 6 | internal enum MichelineConstants { 7 | public static let annotations = "annots" 8 | public static let args = "args" 9 | public static let primitive = "prim" 10 | public static let bytes = "bytes" 11 | public static let int = "int" 12 | public static let left = "Left" 13 | public static let `false` = "False" 14 | public static let none = "None" 15 | public static let pair = "Pair" 16 | public static let right = "Right" 17 | public static let some = "Some" 18 | public static let string = "string" 19 | public static let `true` = "True" 20 | public static let unit = "Unit" 21 | } 22 | 23 | /// An abstract representation of a Michelson param. 24 | /// 25 | /// - SeeAlso: https://tezos.gitlab.io/master/whitedoc/michelson.html 26 | public protocol MichelsonParameter { 27 | /// A dictionary representing the paramater as a JSON object. 28 | var networkRepresentation: JSONCodable { get } 29 | } 30 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | cobertura.xml 2 | 3 | # Created by http://www.gitignore.io 4 | 5 | ### OSX ### 6 | .DS_Store 7 | .AppleDouble 8 | .LSOverride 9 | 10 | # Icon must end with two \r 11 | Icon 12 | 13 | # Thumbnails 14 | ._* 15 | 16 | # Files that might appear on external disk 17 | .Spotlight-V100 18 | .Trashes 19 | 20 | # Directories potentially created on remote AFP share 21 | .AppleDB 22 | .AppleDesktop 23 | Network Trash Folder 24 | Temporary Items 25 | .apdisk 26 | 27 | ### Objective-C ### 28 | # Xcode 29 | # 30 | build/ 31 | *.pbxuser 32 | !default.pbxuser 33 | *.mode1v3 34 | !default.mode1v3 35 | *.mode2v3 36 | !default.mode2v3 37 | *.perspectivev3 38 | !default.perspectivev3 39 | xcuserdata 40 | *.xccheckout 41 | *.moved-aside 42 | DerivedData 43 | *.hmap 44 | *.ipa 45 | *.xcuserstate 46 | 47 | # CocoaPods 48 | Pods/ 49 | 50 | # Carthage 51 | Carthage 52 | Romefile 53 | 54 | #Fastlane 55 | fastlane/report.xml 56 | fastlane/Preview.html 57 | fastlane/screenshots 58 | fastlane/test_output 59 | screenshots 60 | 61 | #build output 62 | /outputs 63 | 64 | # Playgrounds 65 | timeline.xctimeline 66 | playground.xcworkspace 67 | -------------------------------------------------------------------------------- /TezosKit/Utils/JailbreakUtils.swift: -------------------------------------------------------------------------------- 1 | #if canImport(DTTJailbreakDetection) 2 | import DTTJailbreakDetection 3 | #endif 4 | 5 | /// Utilities for working with Jailbroken devices. 6 | public enum JailbreakUtils { 7 | /// Crash if the host device is jailbroken. 8 | /// 9 | /// Jailbroken device have no access control on root files, hence rendering the sandbox mode useless. This potentially exposes keys and can lead to loss 10 | /// of funds. 11 | public static func crashIfJailbroken() { 12 | if JailbreakUtils.isJailBroken() { 13 | fatalError( 14 | """ 15 | Jailbreak detected on host device. Using TezosKit on a jailbroken device may expose your keys and lead to loss 16 | of funds. 17 | """ 18 | ) 19 | } 20 | } 21 | 22 | /// Return whether or not the host device is jailbroken. 23 | /// 24 | /// On non-iOS builds, this function will always return `false`. 25 | public static func isJailBroken() -> Bool { 26 | #if canImport(DTTJailbreakDetection) 27 | return DTTJailbreakDetection.isJailbroken() 28 | #else 29 | return false 30 | #endif 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 Keefer Taylor 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. -------------------------------------------------------------------------------- /Tests/UnitTests/TezosKit/TransactionOperationTest.swift: -------------------------------------------------------------------------------- 1 | // Copyright Keefer Taylor, 2018 2 | 3 | import TezosKit 4 | import XCTest 5 | 6 | // swiftlint:disable force_cast 7 | 8 | class TransactionOperationTest: XCTestCase { 9 | public func testTransation() { 10 | let balance = Tez(3.50) 11 | guard case let .success(operation) = OperationFactory.testFactory.transactionOperation( 12 | amount: balance, 13 | source: .testAddress, 14 | destination: .testDestinationAddress, 15 | operationFeePolicy: .default, 16 | signatureProvider: FakeSignatureProvider.testSignatureProvider 17 | ) else { 18 | XCTFail() 19 | return 20 | } 21 | let dictionary = operation.dictionaryRepresentation 22 | 23 | XCTAssertNotNil(dictionary["source"]) 24 | XCTAssertEqual(dictionary["source"] as? String, .testAddress) 25 | 26 | XCTAssertNotNil(dictionary["destination"]) 27 | XCTAssertEqual(dictionary["destination"] as? String, .testDestinationAddress) 28 | 29 | XCTAssertNotNil(dictionary["amount"]) 30 | XCTAssertEqual(dictionary["amount"] as? String, balance.rpcRepresentation) 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /TezosKit/TezosNode/RPC/Payload/SignedProtocolOperationPayload.swift: -------------------------------------------------------------------------------- 1 | // Copyright Keefer Taylor, 2019. 2 | 3 | import Foundation 4 | 5 | /// An operation payload that is signed and bound to a specific protocol. 6 | public struct SignedProtocolOperationPayload { 7 | /// Retrieve a dictionary representation of the payload. 8 | public var dictionaryRepresentation: [[String: Any]] { 9 | var payload = signedOperationPayload.dictionaryRepresentation 10 | payload["protocol"] = `protocol` 11 | return [payload] 12 | } 13 | 14 | /// An operation payload and associated signature. 15 | private let signedOperationPayload: SignedOperationPayload 16 | 17 | /// The hash of the protocol for the payload. 18 | private let `protocol`: String 19 | 20 | /// - Parameters: 21 | /// - signedOperationPayload: An operation payload and associated signature. 22 | /// - operationMetadata: Metadata for the operation. 23 | public init(signedOperationPayload: SignedOperationPayload, operationMetadata: OperationMetadata) { 24 | self.signedOperationPayload = signedOperationPayload 25 | self.protocol = operationMetadata.`protocol` 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /TezosKit/TezosNode/RPC/Payload/RunOperationPayload.swift: -------------------------------------------------------------------------------- 1 | // Copyright Keefer Taylor, 2019. 2 | 3 | import Foundation 4 | 5 | /// A payload for an running an operation. 6 | public struct RunOperationPayload { 7 | private enum JSON { 8 | public static let chainID = "chain_id" 9 | public static let operation = "operation" 10 | } 11 | 12 | /// The operation payload. 13 | private let signedOperationPayload: SignedOperationPayload 14 | 15 | /// The Chain ID to run on. 16 | private let chainID: String 17 | 18 | /// Retrieve a dictionary representation of the payload. 19 | public var dictionaryRepresentation: [String: Any] { 20 | return [ 21 | JSON.operation: signedOperationPayload.dictionaryRepresentation, 22 | JSON.chainID: chainID 23 | ] 24 | } 25 | 26 | /// - Parameters: 27 | /// - signedOperationPayload: The `SignedOperationPayload` 28 | /// - operationMetadata: The `OperationMetadata`. 29 | public init(signedOperationPayload: SignedOperationPayload, operationMetadata: OperationMetadata) { 30 | self.signedOperationPayload = signedOperationPayload 31 | self.chainID = operationMetadata.chainID 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /TezosKit/Conseil/RPC/GetRecievedSmartContractTransactionsRPC.swift: -------------------------------------------------------------------------------- 1 | // 2 | // GetRecievedSmartContractTransactionsRPC.swift 3 | // TezosKit 4 | // 5 | // Created by Simon Mcloughlin on 09/04/2020. 6 | // 7 | 8 | import Foundation 9 | 10 | /// An RPC which fetches received transactions for an account, that came from smart contracts. For example if an account reiceved some FA1.2 tokens 11 | public class GetReceivedSmartContractTransactionsRPC: ConseilQueryRPC<[Transaction]> { 12 | /// - Parameters: 13 | /// - account: The account to query. 14 | /// - limit: The number of items to return. 15 | public init(account: String, limit: Int) { 16 | let predicates: [ConseilPredicate] = [ 17 | ConseilQuery.Predicates.predicateWith(field: "parameters", operation: .like, set: [account], inverse: false) 18 | ] 19 | let orderBy: ConseilOrderBy = ConseilQuery.OrderBy.orderBy(field: "timestamp") 20 | let query: [String: Any] = ConseilQuery.query(predicates: predicates, orderBy: orderBy, limit: limit) 21 | 22 | super.init( 23 | query: query, 24 | entity: .operation, 25 | responseAdapterClass: TransactionsResponseAdapter.self 26 | ) 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /TezosKit/TezosNode/RPC/Payload/SignedOperationPayload.swift: -------------------------------------------------------------------------------- 1 | // Copyright Keefer Taylor, 2019. 2 | 3 | import Foundation 4 | 5 | /// A payload for an operation and associated signature. 6 | public struct SignedOperationPayload { 7 | /// The operation payload. 8 | private let operationPayload: OperationPayload 9 | 10 | /// The signature for the operation payload. 11 | private let base58Signature: String 12 | 13 | /// Retrieve a dictionary representation of the payload. 14 | public var dictionaryRepresentation: [String: Any] { 15 | var payload = operationPayload.dictionaryRepresentation 16 | payload["signature"] = base58Signature 17 | return payload 18 | } 19 | 20 | /// - Parameters: 21 | /// - operationPayload: The operation payload. 22 | /// - signature: The signature for the operation payload. 23 | public init?(operationPayload: OperationPayload, signature: [UInt8], signingCurve: EllipticalCurve) { 24 | self.operationPayload = operationPayload 25 | guard let base58Signature = CryptoUtils.base58(signature: signature, signingCurve: signingCurve) else { 26 | return nil 27 | } 28 | self.base58Signature = base58Signature 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /TezosKit/Common/Services/Logger.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | /// Log levels for TezosKit. 4 | /// Implicitly ordered from most to least verbose. 5 | public enum LogLevel: Int { 6 | case debug = 0 7 | case info = 1 8 | case none = 2 9 | } 10 | 11 | /// Provides logging functionality in TezosKit. 12 | public class Logger { 13 | /// A shared singleton logging instance. 14 | public static let shared = Logger(logLevel: .none) 15 | 16 | /// The level at which messages will be logged. 17 | public var logLevel: LogLevel 18 | 19 | /// Please use the shared singleton rather than instantiating this class directly. 20 | /// 21 | /// - Parameter logLevel: The level of logs this logger will show. 22 | private init(logLevel: LogLevel) { 23 | self.logLevel = logLevel 24 | } 25 | 26 | /// Log a message. 27 | /// 28 | /// - Parameters: 29 | /// - message: The message to log. 30 | /// - logLevel: The logLevel of the message. 31 | public func log(_ message: String, level: LogLevel) { 32 | // Only log if the log level of the Logger is less than the log level of the mssage 33 | if self.logLevel.rawValue <= level.rawValue { 34 | print(message) 35 | } 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /TezosKit/Crypto/EllipticCurveKeyPair/README.md: -------------------------------------------------------------------------------- 1 | # Elliptic Curve Key Pair 2 | 3 | This library was imported from [agens-no/EllipticCurveKeyPair](https://github.com/agens-no/EllipticCurveKeyPair) on Feb 07, 2020. The library has received updates but was last released (via both GitHub Tags / Carthage and CocoaPods) in November 2017. While Carthage would allow consumption of a fork of the library, CocoaPods requires the owner to publish it. There have been regular updates to this repo, so publishing a new CocoaPods version / forking for Carthage feels less than useful. 4 | 5 | ## Imported Files 6 | 7 | This folder contains the contents of the `Sources/` folder. Headers of the files indicate the commit hash they were imported at. 8 | 9 | ## Tracking a new Release 10 | 11 | If a new version of EllipticCurveKeyPair is released, this library should move to consuming the code as a dependency. [Issue 48](https://github.com/agens-no/EllipticCurveKeyPair/issues/48) tracks this request. 12 | 13 | ## History 14 | ** Please update this if new version are ever imported. ** 15 | 16 | - Feb 09, 2020: [Performed minor fixes and upgrades for Swift 5](https://github.com/keefertaylor/TezosKit/pull/164) 17 | - Feb 07, 2020: Initial import 18 | -------------------------------------------------------------------------------- /TezosKit/Common/Michelson/AbstractMichelsonParameter.swift: -------------------------------------------------------------------------------- 1 | // Copyright Keefer Taylor, 2019. 2 | 3 | import Foundation 4 | 5 | /// Models a swift dictionary as a JSON representation (Micheline) of a Michelson parameter. 6 | /// 7 | /// This abstract base class can be used to create Michelson parameters which TezosKit doesn't support. 8 | public class AbstractMichelsonParameter: MichelsonParameter { 9 | public let networkRepresentation: JSONCodable 10 | 11 | /// - Parameter networkRepresentation: A dictionary representation of the parameter which can be encoded to JSON. 12 | /// - Parameter annotations: Optional annotations 13 | public init(networkRepresentation: [String: Any], annotations: [MichelsonAnnotation]? = nil) { 14 | var annotationAugmentedDictionary = networkRepresentation 15 | if let annotations = annotations { 16 | annotationAugmentedDictionary[MichelineConstants.annotations] = annotations.map { $0.value } 17 | } 18 | 19 | self.networkRepresentation = annotationAugmentedDictionary 20 | } 21 | 22 | /// - Parameter networkRepresentation: An array representation of the parameter which can be encoded to JSON. 23 | public init(networkRepresentation: [Any]) { 24 | self.networkRepresentation = networkRepresentation 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /TezosKit/Utils/MnemonicUtil.swift: -------------------------------------------------------------------------------- 1 | // Copyright Keefer Taylor, 2018 2 | 3 | import MnemonicKit 4 | 5 | /// A static utility wrapper which provides a facade for a mnemonic library. 6 | public enum MnemonicUtil { 7 | /// Generate a mnemonic. 8 | public static func generateMnemonic() -> String? { 9 | return Mnemonic.generateMnemonic(strength: 128) 10 | } 11 | 12 | /// Generate a seed string from a given mnemonic. 13 | /// 14 | /// - Parameters: 15 | /// - mnemonic: A BIP39 mnemonic phrase. 16 | /// - passphrase: An optional passphrase used for encryption. 17 | public static func seedString(from mnemonic: String, passphrase: String = "") -> String? { 18 | guard let rawSeedString = 19 | Mnemonic.deterministicSeedString(from: mnemonic, passphrase: passphrase) else { 20 | return nil 21 | } 22 | return String(rawSeedString[.. Bool { 31 | return Mnemonic.validate(mnemonic: mnemonic) 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /.swiftlint.yml: -------------------------------------------------------------------------------- 1 | disabled_rules: 2 | - file_length 3 | - function_parameter_count 4 | - function_body_length 5 | - nesting 6 | - type_body_length 7 | - xctfail_message 8 | 9 | opt_in_rules: 10 | - attributes 11 | - closure_end_indentation 12 | - closure_spacing 13 | - empty_count 14 | - empty_string 15 | - explicit_init 16 | - file_header 17 | - first_where 18 | # TODO: Fix force unwrapping in XCTests, then enable. 19 | # - force_unwrapping 20 | - implicitly_unwrapped_optional 21 | - literal_expression_end_indentation 22 | - multiline_arguments 23 | - multiline_arguments_brackets 24 | - multiline_literal_brackets 25 | - multiline_parameters 26 | - multiline_parameters_brackets 27 | - multiple_closures_with_trailing_closure 28 | - no_extension_access_modifier 29 | - number_separator 30 | - object_literal 31 | - operator_usage_whitespace 32 | - overridden_super_call 33 | - prohibited_super_call 34 | - redundant_nil_coalescing 35 | - sorted_imports 36 | - trailing_closure 37 | - vertical_parameter_alignment_on_call 38 | # TODO: Documentation. 39 | 40 | excluded: 41 | - Base58String/ 42 | - Carthage/ 43 | - Pods/ 44 | 45 | identifier_name: 46 | min_length: 1 47 | 48 | file_header: 49 | required_pattern: Copyright Keefer Taylor, 201(8|9) 50 | -------------------------------------------------------------------------------- /docs/PromiseKit.md: -------------------------------------------------------------------------------- 1 | # PromiseKit 2 | 3 | The core of TezosKit provides closure style callbacks. 4 | 5 | An extension of the library provides [PromiseKit](https://github.com/mxcl/PromiseKit) functionality. In CocoaPods, PromiseKit is split into a separate module. In Carthage, all code is delivered as a single module. 6 | 7 | ### Using Promises 8 | 9 | In general, any call you can make on a `TezosNodeClient` or `ConseilClient` can also be done with promises. 10 | 11 | For instance, the following closure style callbacks: 12 | 13 | ```swift 14 | let tezosNodeClient = TezosClient() 15 | 16 | // Closure completion handler 17 | tezosNodeClient.getBalance(address: "KT1BVAXZQUc4BGo3WTJ7UML6diVaEbe4bLZA") { result in 18 | switch result { 19 | case .success(let balance): 20 | print("The balance of the contract is \(balance.humanReadableRepresentation)") 21 | case .failure(let error): 22 | print("Error getting balance: \(error)") 23 | } 24 | } 25 | ``` 26 | 27 | Are equivalent to: 28 | 29 | ```swift 30 | let tezosNodeClient = TezosClient() 31 | 32 | // PromiseKit Promises 33 | tezosNodeClient.getBalance(address: "KT1BVAXZQUc4BGo3WTJ7UML6diVaEbe4bLZA").done { balance in 34 | print("The balance of the contract is \(balance.humanReadableRepresentation)") 35 | } .catch { _ in 36 | print("Couldn't get balance.") 37 | } 38 | ``` 39 | -------------------------------------------------------------------------------- /TezosKit/TezosNode/Models/Operation/RevealOperation.swift: -------------------------------------------------------------------------------- 1 | // Copyright Keefer Taylor, 2018 2 | 3 | import Foundation 4 | 5 | /// An operation to reveal an address. 6 | /// 7 | /// - Note: TezosKit will automatically inject this operation when required for supported operations. 8 | public class RevealOperation: AbstractOperation { 9 | /// The public key for the address being revealed. 10 | private let publicKey: PublicKeyProtocol 11 | 12 | public override var dictionaryRepresentation: [String: Any] { 13 | var operation = super.dictionaryRepresentation 14 | operation["public_key"] = publicKey.base58CheckRepresentation 15 | return operation 16 | } 17 | 18 | /// Initialize a new reveal operation. 19 | /// 20 | /// - Parameters: 21 | /// - address: The address to reveal. 22 | /// - publicKey: The public key of the address to reveal. 23 | /// - operationFees: OperationFees for the transaction. 24 | public init(from address: Address, publicKey: PublicKeyProtocol, operationFees: OperationFees) { 25 | self.publicKey = publicKey 26 | super.init(source: address, kind: .reveal, operationFees: operationFees) 27 | } 28 | 29 | public override func mutableCopy(with zone: NSZone? = nil) -> Any { 30 | return RevealOperation(from: source, publicKey: publicKey, operationFees: operationFees) 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /TezosKit/Crypto/Base58+TezosCrypto.swift: -------------------------------------------------------------------------------- 1 | // Copyright Keefer Taylor, 2019. 2 | 3 | import Base58Swift 4 | import Foundation 5 | 6 | /// Helper functions on Base58Swift. 7 | extension Base58 { 8 | /// Encode a Base58Check string from the given message and prefix. 9 | /// 10 | /// The returned address is a Base58 encoded String with the following format: [prefix][key][4 byte checksum] 11 | public static func encode(message: [UInt8], prefix: [UInt8]) -> String { 12 | let prefixedMessage = prefix + message 13 | return Base58.base58CheckEncode(prefixedMessage) 14 | } 15 | 16 | /// Decode a Base58Check string to bytes and verify that the bytes begin with the given prefix. 17 | /// 18 | /// - Parameters: 19 | /// - string: The Base58Check string to decode. 20 | /// - prefix: The expected prefix bytes after decoding. 21 | /// - Returns: The raw bytes without the given prefix if the string was valid Base58Check and had the expected prefix, 22 | /// otherwise, nil. 23 | public static func base58CheckDecodeWithPrefix(string: String, prefix: [UInt8]) -> [UInt8]? { 24 | guard 25 | let bytes = Base58.base58CheckDecode(string), 26 | bytes.prefix(prefix.count).elementsEqual(prefix) 27 | else { 28 | return nil 29 | } 30 | return Array(bytes.suffix(from: prefix.count)) 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /TezosKit/TezosNode/RPC/Payload/OperationPayload.swift: -------------------------------------------------------------------------------- 1 | // Copyright Keefer Taylor, 2019. 2 | 3 | import Foundation 4 | 5 | /// A payload that can be forged into operation bytes. 6 | public struct OperationPayload { 7 | /// An array of dictionaries representing operations. 8 | internal let operations: [OperationWithCounter] 9 | 10 | /// The hash of the head of the chain to apply the operation on. 11 | internal let branch: String 12 | 13 | /// Retrieve a dictionary representation of the payload. 14 | public var dictionaryRepresentation: [String: Any] { 15 | var contents: [[String: Any]] = [] 16 | for operation in operations { 17 | contents.append(operation.dictionaryRepresentation) 18 | } 19 | return [ 20 | "contents": contents, 21 | "branch": branch 22 | ] 23 | } 24 | 25 | /// Creates a operation payload from a list of operations. 26 | /// 27 | /// This initializer will automatically add reveal operations and set address counters properly. 28 | /// 29 | /// - Parameters: 30 | /// - operations: A list of operations to forge. 31 | /// - operationMetadata: Metadata about the operations. 32 | public init( 33 | operations: [OperationWithCounter], 34 | operationMetadata: OperationMetadata 35 | ) { 36 | self.operations = operations 37 | self.branch = operationMetadata.branch 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /TezosKit/Utils/JSONUtils.swift: -------------------------------------------------------------------------------- 1 | // Copyright Keefer Taylor, 2018 2 | 3 | import Foundation 4 | 5 | /// Marks an object as codable to JSON. 6 | public protocol JSONCodable {} 7 | 8 | /// Mark arrays and dictionaries as JSONCodable. 9 | extension Dictionary: JSONCodable where Key == String {} 10 | extension Array: JSONCodable {} 11 | 12 | /// A facade for a JSON parsing library. 13 | public enum JSONUtils { 14 | /// Returns a JSON encoded string representation of a given string. 15 | public static func jsonString(for string: String) -> String? { 16 | return "\"" + string + "\"" 17 | } 18 | 19 | /// Return a JSON encoded string representation of a given integer. 20 | public static func jsonString(for int: Int) -> String? { 21 | return jsonString(for: String(int)) 22 | } 23 | 24 | /// Return a JSON encoded sstring representation of the given codable. 25 | public static func jsonString(for object: JSONCodable) -> String? { 26 | do { 27 | var options: JSONSerialization.WritingOptions = [] 28 | if #available(iOS 11.0, OSX 10.13, *) { 29 | options = [.sortedKeys] 30 | } 31 | let jsonData = try JSONSerialization.data(withJSONObject: object, options: options) 32 | guard let jsonPayload = String(data: jsonData, encoding: .utf8) else { 33 | return nil 34 | } 35 | return jsonPayload 36 | } catch { 37 | return nil 38 | } 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /Tests/UnitTests/TezosKit/FeeEstimatorTest.swift: -------------------------------------------------------------------------------- 1 | // Copyright Keefer Taylor, 2019. 2 | 3 | import TezosKit 4 | import XCTest 5 | 6 | final class FeeEstimatorTest: XCTestCase { 7 | func testEstimateFees() { 8 | let address = Address.testAddress 9 | let signatureProvider = FakeSignatureProvider.testSignatureProvider 10 | let operationFactory = OperationFactory.testFactory 11 | guard 12 | case let .success(operation) = operationFactory.delegateOperation( 13 | source: address, 14 | to: .testDestinationAddress, 15 | operationFeePolicy: .default, 16 | signatureProvider: signatureProvider 17 | ) 18 | else { 19 | XCTFail() 20 | return 21 | } 22 | 23 | let feeEstimator = FeeEstimator( 24 | forgingService: .testForgingService, 25 | operationMetadataProvider: .testOperationMetadataProvider, 26 | simulationService: .testSimulationService 27 | ) 28 | 29 | let completionExpectation = XCTestExpectation(description: "completion called") 30 | 31 | feeEstimator.estimate(operation: operation, address: address, signatureProvider: signatureProvider) { result in 32 | switch result { 33 | case .success: 34 | completionExpectation.fulfill() 35 | case .failure: 36 | XCTFail() 37 | } 38 | } 39 | 40 | wait(for: [completionExpectation], timeout: .expectationTimeout) 41 | 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /TezosKit/Crypto/Prefixes.swift: -------------------------------------------------------------------------------- 1 | // Copyright Keefer Taylor, 2019 2 | 3 | import Foundation 4 | 5 | /// Common prefixes used across Tezos Cryptography. 6 | public enum Prefix { 7 | public enum Watermark { 8 | public static let operation: [UInt8] = [ 3 ] // 03 9 | } 10 | 11 | public enum Keys { 12 | public enum Ed25519 { 13 | public static let `public`: [UInt8] = [13, 15, 37, 217] // edpk 14 | public static let secret: [UInt8] = [43, 246, 78, 7] // edsk 15 | public static let seed: [UInt8] = [13, 15, 58, 7] // edsk 16 | public static let signature: [UInt8] = [9, 245, 205, 134, 18] // edsig 17 | } 18 | 19 | public enum P256 { 20 | public static let secret: [UInt8] = [16, 81, 238, 189] // p2sk 21 | public static let `public`: [UInt8] = [3, 178, 139, 127] // p2pk 22 | public static let signature: [UInt8] = [54, 240, 44, 52] // p2sig 23 | } 24 | 25 | public enum Secp256k1 { 26 | public static let `public`: [UInt8] = [3, 254, 226, 86] // sppk 27 | public static let secret: [UInt8] = [17, 162, 224, 201] // spsk 28 | public static let signature: [UInt8] = [13, 115, 101, 19, 63] // spsig 29 | } 30 | } 31 | 32 | public enum Address { 33 | public static let tz1: [UInt8] = [6, 161, 159] // tz1 34 | public static let tz2: [UInt8] = [6, 161, 161] // tz2 35 | public static let tz3: [UInt8] = [6, 161, 164] // tz3 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /Tests/UnitTests/TezosKit/JSONArrayResponseAdapterTest.swift: -------------------------------------------------------------------------------- 1 | // Copyright Keefer Taylor, 2018 2 | 3 | import TezosKit 4 | import XCTest 5 | 6 | class JSONArrayResponseAdapterTest: XCTestCase { 7 | public func testParseArray() { 8 | let validJSONString = "[{\"a\": \"b\"}, {\"c\": \"d\"}, {\"e\": \"f\"}]" 9 | guard let validJSONData = validJSONString.data(using: .utf8), 10 | let parsedArray = JSONArrayResponseAdapter.parse(input: validJSONData) else { 11 | XCTFail() 12 | return 13 | } 14 | 15 | XCTAssertEqual(parsedArray.count, 3) 16 | XCTAssertNotNil(parsedArray[0]) 17 | XCTAssertNotNil(parsedArray[1]) 18 | XCTAssertNotNil(parsedArray[2]) 19 | } 20 | 21 | public func testParseArrayWithDictionary() { 22 | let validJSONString = "{\"a\": \"b\", \"c\": { \"d\": \"e\" }}" 23 | guard let validJSONData = validJSONString.data(using: .utf8) else { 24 | XCTFail() 25 | return 26 | } 27 | 28 | let parsedArray = JSONArrayResponseAdapter.parse(input: validJSONData) 29 | XCTAssertNil(parsedArray) 30 | } 31 | 32 | public func testParseDictionaryWithInvalidJSON() { 33 | let validJSONString = "abc:[]123" 34 | guard let validJSONData = validJSONString.data(using: .utf8) else { 35 | XCTFail() 36 | return 37 | } 38 | 39 | let parsedArray = JSONArrayResponseAdapter.parse(input: validJSONData) 40 | XCTAssertNil(parsedArray) 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /TezosKit/TezosNode/Models/Operation/AbstractOperation.swift: -------------------------------------------------------------------------------- 1 | // Copyright Keefer Taylor, 2018 2 | 3 | import Foundation 4 | 5 | /// An abstract super class representing an operation to perform on the blockchain. Common parameters across operations 6 | /// and default parameter values are provided by the abstract class's implementation. 7 | public class AbstractOperation: Operation { 8 | public let source: Address 9 | public let kind: OperationKind 10 | public var operationFees: OperationFees 11 | 12 | public var requiresReveal: Bool { 13 | switch kind { 14 | case .delegation, .transaction, .origination: 15 | return true 16 | case .reveal: 17 | return false 18 | } 19 | } 20 | 21 | public var dictionaryRepresentation: [String: Any] { 22 | var operation: [String: String] = [:] 23 | operation["kind"] = kind.rawValue 24 | operation["source"] = source 25 | 26 | operation["storage_limit"] = String(operationFees.storageLimit) 27 | operation["gas_limit"] = String(operationFees.gasLimit) 28 | operation["fee"] = operationFees.fee.rpcRepresentation 29 | 30 | return operation 31 | } 32 | 33 | public init(source: Address, kind: OperationKind, operationFees: OperationFees) { 34 | self.source = source 35 | self.kind = kind 36 | self.operationFees = operationFees 37 | } 38 | } 39 | 40 | extension AbstractOperation: NSMutableCopying { 41 | public func mutableCopy(with zone: NSZone? = nil) -> Any { 42 | return AbstractOperation(source: source, kind: kind, operationFees: operationFees) 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /TezosKit/TezosNode/RPC/GetBigMapValueRPC.swift: -------------------------------------------------------------------------------- 1 | // Copyright Keefer Taylor, 2019 2 | 3 | import Foundation 4 | 5 | private enum GetBigMapValueJSONConstants { 6 | public static let key = "key" 7 | public static let prim = "prim" 8 | public static let type = "type" 9 | } 10 | 11 | /// An RPC that will retrieve the value of a big map for the given key. 12 | public class GetBigMapValueRPC: RPC<[String: Any]> { 13 | /// - Parameters: 14 | /// - address: The address of a smart contract with a big map. 15 | /// - key: The key in the big map to look up. 16 | /// - type: The michelson type of the key. 17 | public init(address: Address, key: MichelsonParameter, type: MichelsonComparable) { 18 | let endpoint = "/chains/main/blocks/head/context/contracts/" + address + "/big_map_get" 19 | 20 | let payload = JSONUtils.jsonString( 21 | for: [ 22 | GetBigMapValueJSONConstants.key: key.networkRepresentation, 23 | GetBigMapValueJSONConstants.type: MichelsonComparable.networkRepresentation(for: type) 24 | ] 25 | ) 26 | 27 | // The TezosNodeClient returns a HTTP 415 error if this header is not included with the payload. This behavior is 28 | // unique to this RPC. Adding this header to other RPCs causes them to fail with HTTP 415. ¯\_(ツ)_/¯ 29 | let headers = [ 30 | Header.contentTypeApplicationJSON 31 | ] 32 | 33 | super.init( 34 | endpoint: endpoint, 35 | headers: headers, 36 | responseAdapterClass: JSONDictionaryResponseAdapter.self, 37 | payload: payload 38 | ) 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /TezosKit.podspec: -------------------------------------------------------------------------------- 1 | Pod::Spec.new do |s| 2 | s.name = "TezosKit" 3 | s.version = "5.2.1" 4 | s.summary = "TezosKit provides a Swift based toolchain for interacting with the Tezos blockchain" 5 | s.description = <<-DESC 6 | TezosKit provides utilities for interacting with the Tezos Blockchain over an RPC API. 7 | DESC 8 | 9 | s.homepage = "https://github.com/keefertaylor/TezosKit" 10 | s.license = { :type => "MIT", :file => "LICENSE" } 11 | s.author = { "Keefer Taylor" => "keefer@keefertaylor.com" } 12 | s.source = { :git => "https://github.com/keefertaylor/TezosKit.git", :tag => "5.2.1" } 13 | s.source_files = "TezosKit/**/*.swift" 14 | s.swift_version = "5.1" 15 | s.ios.deployment_target = "10.0" 16 | s.osx.deployment_target = "10.14" 17 | 18 | s.source_files = [ "TezosKit/**/*.swift", "Extensions/PromiseKit/*.swift"] 19 | s.frameworks = 'Foundation', "Security" 20 | 21 | s.ios.deployment_target = '10.0' 22 | s.osx.deployment_target = '10.14' 23 | 24 | s.dependency "BigInt", "~> 5.0.0" 25 | s.dependency "MnemonicKit", "~> 1.3.12" 26 | s.dependency "PromiseKit", "~> 6.13.1" 27 | s.dependency "Base58Swift", "~> 2.1.10" 28 | s.dependency "CryptoSwift", "~> 1.3.0" 29 | s.dependency "Sodium", "~> 0.8.0" 30 | s.dependency "secp256k1.swift", "~> 0.1.4" 31 | s.ios.dependency "DTTJailbreakDetection", "~> 0.4.0" 32 | 33 | s.test_spec "Tests" do |test_spec| 34 | test_spec.source_files = ["Tests/Common/*.swift", "Tests/TezosKit/*.swift", "Tests/Extensions/PromiseKit/*.swift"] 35 | end 36 | end 37 | -------------------------------------------------------------------------------- /TezosKit/TezosNode/RPC/Payload/PackDataPayload.swift: -------------------------------------------------------------------------------- 1 | // Copyright Keefer Taylor, 2020 2 | 3 | import Foundation 4 | 5 | /// A payload that can be used to pack data. 6 | public struct PackDataPayload { 7 | // JSON keys and values 8 | private enum JSON { 9 | public enum Keys { 10 | public static let data = "data" 11 | public static let gas = "gas" 12 | public static let prim = "prim" 13 | public static let type = "type" 14 | } 15 | public enum Values { 16 | public static let gasAmount = "8000" 17 | } 18 | } 19 | 20 | // Data to place in the payload. 21 | private let michelsonParameter: MichelsonParameter 22 | private let michelsonComparable: MichelsonComparable 23 | 24 | /// Retrieve a dictionary representation of the payload. 25 | public var dictionaryRepresentation: [String: Any] { 26 | let dictionary: [String: Any] = [ 27 | JSON.Keys.gas: JSON.Values.gasAmount, 28 | JSON.Keys.data: michelsonParameter.networkRepresentation, 29 | JSON.Keys.type: MichelsonComparable.networkRepresentation(for: michelsonComparable) 30 | ] 31 | return dictionary 32 | } 33 | 34 | /// Creates a pack data payload with the given inputs. 35 | /// 36 | /// - Parameters: 37 | /// - michelsonParameter: The parameter to pack. 38 | /// - michelsonComparable: The type of the parameter. 39 | public init( 40 | michelsonParameter: MichelsonParameter, 41 | michelsonComparable: MichelsonComparable 42 | ) { 43 | self.michelsonParameter = michelsonParameter 44 | self.michelsonComparable = michelsonComparable 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /Tests/UnitTests/TezosKit/StringResponseAdapterTest.swift: -------------------------------------------------------------------------------- 1 | // Copyright Keefer Taylor, 2018 2 | 3 | import TezosKit 4 | import XCTest 5 | 6 | class StringResponseAdapterTest: XCTestCase { 7 | public func testParseString() { 8 | let string = "String" 9 | guard let data = string.data(using: .utf8) else { 10 | XCTFail() 11 | return 12 | } 13 | 14 | let result = StringResponseAdapter.parse(input: data) 15 | 16 | XCTAssertEqual(result, string) 17 | } 18 | 19 | // Expect quotes to be stripped. 20 | public func testQuotedString() { 21 | let string = "String" 22 | let quotedString = "\"" + string + "\"" 23 | guard let data = quotedString.data(using: .utf8) else { 24 | XCTFail() 25 | return 26 | } 27 | 28 | let result = StringResponseAdapter.parse(input: data) 29 | 30 | XCTAssertEqual(result, string) 31 | } 32 | 33 | // Expect whitespace to be stripped. 34 | public func testWhitespaceString() { 35 | let string = "String" 36 | let quotedString = " " + string + "\n \n" 37 | guard let data = quotedString.data(using: .utf8) else { 38 | XCTFail() 39 | return 40 | } 41 | 42 | let result = StringResponseAdapter.parse(input: data) 43 | 44 | XCTAssertEqual(result, string) 45 | } 46 | 47 | // Test decode fails on non utf8 string 48 | public func testUnexpectedEncoding() { 49 | let string = "🙃" 50 | guard let data = string.data(using: .utf16) else { 51 | XCTFail() 52 | return 53 | } 54 | 55 | let result = StringResponseAdapter.parse(input: data) 56 | XCTAssertNil(result) 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /Tests/UnitTests/TezosKit/InjectionServiceTest.swift: -------------------------------------------------------------------------------- 1 | // Copyright Keefer Taylor, 2019 2 | 3 | import TezosKit 4 | import XCTest 5 | 6 | final class InjectionServiceTest: XCTestCase { 7 | func testInjectionService() { 8 | let networkClient = FakeNetworkClient.tezosNodeNetworkClient 9 | let injectionService = InjectionService(networkClient: networkClient) 10 | let hex = String.testSignedBytesForInjection 11 | 12 | let expectation = XCTestExpectation(description: "injection completion called") 13 | injectionService.inject(payload: hex) { result in 14 | switch result { 15 | case .success: 16 | expectation.fulfill() 17 | case .failure: 18 | XCTFail() 19 | } 20 | } 21 | 22 | wait(for: [expectation], timeout: .expectationTimeout) 23 | } 24 | 25 | // swiftlint:disable force_cast 26 | 27 | func testInjectionServiceBadResponse() { 28 | let networkClient = FakeNetworkClient.tezosNodeNetworkClient.copy() as! FakeNetworkClient 29 | networkClient.endpointToResponseMap["/injection/operation"] = nil 30 | let injectionService = InjectionService(networkClient: networkClient) 31 | let hex = String.testSignedBytesForInjection 32 | 33 | let expectation = XCTestExpectation(description: "injection completion called") 34 | injectionService.inject(payload: hex) { result in 35 | switch result { 36 | case .success: 37 | XCTFail() 38 | case .failure: 39 | expectation.fulfill() 40 | } 41 | } 42 | 43 | wait(for: [expectation], timeout: .expectationTimeout) 44 | } 45 | 46 | // swiftlint:enable force_cast 47 | 48 | } 49 | -------------------------------------------------------------------------------- /TezosKit/TezosNode/Models/Operation/TransactionOperation.swift: -------------------------------------------------------------------------------- 1 | // Copyright Keefer Taylor, 2018 2 | 3 | import Foundation 4 | 5 | /// An operation to transact XTZ between addresses. 6 | public class TransactionOperation: AbstractOperation { 7 | private enum JSON { 8 | public enum Keys { 9 | public static let amount = "amount" 10 | public static let destination = "destination" 11 | public static let value = "value" 12 | } 13 | } 14 | 15 | internal let amount: Tez 16 | internal let destination: Address 17 | 18 | public override var dictionaryRepresentation: [String: Any] { 19 | var operation = super.dictionaryRepresentation 20 | operation[TransactionOperation.JSON.Keys.amount] = amount.rpcRepresentation 21 | operation[TransactionOperation.JSON.Keys.destination] = destination 22 | 23 | return operation 24 | } 25 | 26 | /// - Parameters: 27 | /// - amount: The amount of XTZ to transact. 28 | /// - from: The address that is sending the XTZ. 29 | /// - to: The address that is receiving the XTZ. 30 | /// - operationFees: OperationFees for the transaction. 31 | public init( 32 | amount: Tez, 33 | source: Address, 34 | destination: Address, 35 | operationFees: OperationFees 36 | ) { 37 | self.amount = amount 38 | self.destination = destination 39 | 40 | super.init(source: source, kind: .transaction, operationFees: operationFees) 41 | } 42 | 43 | public override func mutableCopy(with zone: NSZone? = nil) -> Any { 44 | return TransactionOperation( 45 | amount: amount, 46 | source: source, 47 | destination: destination, 48 | operationFees: operationFees 49 | ) 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /Examples/SecureEnclave/Base.lproj/Main.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 | -------------------------------------------------------------------------------- /Tests/UnitTests/TezosKit/JSONDictionaryResponseAdapterTest.swift: -------------------------------------------------------------------------------- 1 | // Copyright Keefer Taylor, 2018 2 | 3 | import TezosKit 4 | import XCTest 5 | 6 | class JSONDictionaryResponseAdapterTest: XCTestCase { 7 | public func testParseDictionary() { 8 | let validJSONString = "{\"a\": \"b\", \"c\": { \"d\": \"e\" }}" 9 | guard let validJSONData = validJSONString.data(using: .utf8), 10 | let parsedDictionary = JSONDictionaryResponseAdapter.parse(input: validJSONData) else { 11 | XCTFail() 12 | return 13 | } 14 | 15 | XCTAssertNotNil(parsedDictionary["a"]) 16 | guard let a = parsedDictionary["a"] as? String else { 17 | XCTFail() 18 | return 19 | } 20 | XCTAssertEqual(a, "b") 21 | 22 | XCTAssertNotNil(parsedDictionary["c"]) 23 | guard let c = parsedDictionary["c"] as? [String: String], 24 | let d = c["d"] else { 25 | XCTFail() 26 | return 27 | } 28 | XCTAssertEqual(d, "e") 29 | } 30 | 31 | public func testParseDictionaryWithArray() { 32 | let validJSONString = "[{\"a\": \"b\", \"c\": { \"d\": \"e\" }}]" 33 | guard let validJSONData = validJSONString.data(using: .utf8) else { 34 | XCTFail() 35 | return 36 | } 37 | 38 | let parsedDictionary = JSONDictionaryResponseAdapter.parse(input: validJSONData) 39 | XCTAssertNil(parsedDictionary) 40 | } 41 | 42 | public func testParseDictionaryWithInvalidJSON() { 43 | let validJSONString = "abc:[]123" 44 | guard let validJSONData = validJSONString.data(using: .utf8) else { 45 | XCTFail() 46 | return 47 | } 48 | 49 | let parsedDictionary = JSONDictionaryResponseAdapter.parse(input: validJSONData) 50 | XCTAssertNil(parsedDictionary) 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /TezosKit/Common/Models/SecureEnclaveWallet.swift: -------------------------------------------------------------------------------- 1 | // Copyright Keefer Taylor, 2020. 2 | 3 | import Foundation 4 | 5 | /// A wallet which stores keys in a device's secure enclave. 6 | /// 7 | /// WARNING: Keys generated in the secure enclave are not able to be backed up. Additionally, iOS may choose to remove these keys at it's discretion, including 8 | /// when biometrics on the device are changed, a the device is restored, or the host app is deleted. This wallet should only be used as part of a 9 | /// multisignature signing scheme with a proper backup. 10 | /// Read more: https://medium.com/@keefertaylor/signing-tezos-transactions-with-ioss-secure-enclave-and-face-id-6166a752519?source=your_stories_page--------------------------- 11 | @available(OSX 10.12.1, iOS 9.0, *) 12 | public class SecureEnclaveWallet: DeviceWallet { 13 | /// Labels for keys in the enclave. 14 | private enum KeyLabels { 15 | public static let `public` = "TezosKitEnclave.public" 16 | public static let `private` = "TezosKitEnclave.private" 17 | } 18 | 19 | /// Returns whether the device contains a secure enclave. 20 | public static var deviceHasSecureEnclave: Bool { 21 | return EllipticCurveKeyPair.Device.hasSecureEnclave 22 | } 23 | 24 | /// - Parameter prompt: A prompt to use when asking the wallet to sign bytes. 25 | public init?(prompt: String) { 26 | // Ensure that the device has access to a secure enclave. 27 | guard SecureEnclaveWallet.deviceHasSecureEnclave else { 28 | return nil 29 | } 30 | 31 | super.init( 32 | prompt: prompt, 33 | token: .secureEnclave, 34 | publicKeyLabel: KeyLabels.public, 35 | privateKeyLabel: KeyLabels.private 36 | ) 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /docs/Fees.md: -------------------------------------------------------------------------------- 1 | # Fees 2 | Fees are an advanced topic which most users of TezosKit will not need to know about. 3 | 4 | Every `Operation` contains an `OperationFees` struct which represents a set of fees (fee, gas limit, storage limit) that will be applied when it is injected into the network. 5 | 6 | All calls on `TezosNodeClient` that injects an operation on the network provide an opportunity to provide a custom fee structure. If no fee structure is provided, the default fees are used for the operation. 7 | 8 | ### Defaults 9 | 10 | TezosNodeClient will provide default fees for the `TezosProtocol` version that is provided at initialization time. A `DefaultFeeProvider` object provides fees for a given operation type and `TezosProtocol` version. 11 | 12 | ### Custom Fees 13 | 14 | #### TezosNodeClient Operations 15 | 16 | Overriding the fees on an operation supported by the TezosNodeClient is easy. Simply pass a value for the fees parameter: 17 | ```swift 18 | // Create a node client 19 | let tezosNodeClient = TezosNodeClient(...) 20 | 21 | // Create a custom OperationFees object 22 | let customOperationFees = OperationFees(...) 23 | 24 | // Inject an operation with a custom fee. 25 | tezsosNodeClient.delegate( 26 | from: ... 27 | to: ... 28 | keys: ... 29 | operationFees: customOperationFees, 30 | ) { result in 31 | // Handle callback 32 | } 33 | ``` 34 | 35 | #### Custom Operations 36 | 37 | If you are writing custom `Operation` objects, then the default fees for that operation are provided by your implementation. See [Operations](Operations.md) for more details. 38 | 39 | ### Estimated Gas Limit 40 | 41 | The Tezos network supports estimating the Gas used in an operation. This functionality will be supported in TezosKit in a future update. 42 | -------------------------------------------------------------------------------- /Examples/SecureEnclave/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 | -------------------------------------------------------------------------------- /Tests/UnitTests/TezosKit/TokenContractClientTests.swift: -------------------------------------------------------------------------------- 1 | // Copyright Keefer Taylor, 2019. 2 | 3 | @testable import TezosKit 4 | import XCTest 5 | 6 | final class TokenContractClientTests: XCTestCase { 7 | private var tokenContractClient: TokenContractClient? 8 | 9 | override func setUp() { 10 | super.setUp() 11 | 12 | let contract = Address.testTokenContractAddress 13 | let networkClient = FakeNetworkClient.tezosNodeNetworkClient 14 | 15 | let tezosNodeClient = TezosNodeClient(networkClient: networkClient) 16 | tokenContractClient = TokenContractClient( 17 | tokenContractAddress: contract, 18 | tezosNodeClient: tezosNodeClient 19 | ) 20 | } 21 | 22 | func testTransferTokens() { 23 | let expectation = XCTestExpectation(description: "completion called") 24 | 25 | tokenContractClient?.transferTokens( 26 | from: Address.testAddress, 27 | to: Address.testDestinationAddress, 28 | numTokens: 1, 29 | operationFeePolicy: .custom(OperationFees.testFees), 30 | signatureProvider: FakeSignatureProvider.testSignatureProvider 31 | ) { result in 32 | switch result { 33 | case .success: 34 | expectation.fulfill() 35 | case .failure: 36 | XCTFail() 37 | } 38 | } 39 | 40 | wait(for: [expectation], timeout: .expectationTimeout) 41 | } 42 | 43 | func testGetBalance() { 44 | let expectation = XCTestExpectation(description: "completion called") 45 | 46 | tokenContractClient?.getTokenBalance(address: Address.testAddress) { result in 47 | switch result { 48 | case .success: 49 | expectation.fulfill() 50 | case .failure: 51 | XCTFail() 52 | } 53 | } 54 | 55 | wait(for: [expectation], timeout: .expectationTimeout) 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /TezosKit/Common/Michelson/IntMichelsonParameter.swift: -------------------------------------------------------------------------------- 1 | // Copyright Keefer Taylor, 2019. 2 | 3 | import BigInt 4 | import Foundation 5 | 6 | /// A representation of an integer parameter in Michelson. 7 | public class IntMichelsonParameter: AbstractMichelsonParameter { 8 | /// Initialize a representation of an integer using an `Int`. 9 | /// 10 | /// Note that the michelson int type is unbounded while `Int` values have bounded precision. Consider using the `init(bigInt:annotations:)` 11 | /// to represent a full range of values. 12 | public convenience init(int: Int, annotations: [MichelsonAnnotation]? = nil) { 13 | self.init(string: String(int), annotations: annotations) 14 | } 15 | 16 | /// Initialize a representation of an integer using a `Decimal`. 17 | /// 18 | /// Note that the michelson int type is unbounded while `Decimal` values have bounded precision. Consider using the `init(bigInt:annotations:)` 19 | /// to represent a full range of values. 20 | public convenience init(decimal: Decimal, annotations: [MichelsonAnnotation]? = nil) { 21 | self.init(string: "\(decimal)", annotations: annotations) 22 | } 23 | 24 | /// Initialize a representation of an integer using an `BigInt`. 25 | public convenience init(bigInt: BigInt, annotations: [MichelsonAnnotation]? = nil) { 26 | self.init(string: String(bigInt), annotations: annotations) 27 | } 28 | 29 | /// Internal initializer. 30 | /// 31 | /// - Parameters: 32 | /// - string: A numerical string representing the precision. 33 | /// - annotations: An array of annotations to apply to the parameter. Defaults to no annotations. 34 | private init(string: String, annotations: [MichelsonAnnotation]? = nil) { 35 | super.init(networkRepresentation: [MichelineConstants.int: string], annotations: annotations) 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /TezosKit/TezosNode/Services/OperationPayloadFactory.swift: -------------------------------------------------------------------------------- 1 | // Copyright Keefer Taylor, 2019 2 | 3 | import Foundation 4 | 5 | /// A factory which can produce operation payloads 6 | public enum OperationPayloadFactory { 7 | /// Create an operation payload from the given inputs. 8 | public static func operationPayload( 9 | from operations: [Operation], 10 | source: Address, 11 | signatureProvider: SignatureProvider, 12 | operationMetadata: OperationMetadata 13 | ) -> OperationPayload? { 14 | // Determine if the address performing the operations has been revealed. If it has not been, check if any of the 15 | // operations to perform requires the address to be revealed. If so, prepend a reveal operation to the operations to 16 | // perform. 17 | var mutableOperations = operations 18 | if operationMetadata.key == nil && operations.first(where: { $0.requiresReveal }) != nil { 19 | let defaultRevealFees = DefaultFeeProvider.fees(for: .reveal) 20 | let revealOperation = RevealOperation( 21 | from: source, 22 | publicKey: signatureProvider.publicKey, 23 | operationFees: defaultRevealFees 24 | ) 25 | 26 | mutableOperations.insert(revealOperation, at: 0) 27 | } 28 | 29 | // Process all operations to have increasing counters and place them in the contents array. 30 | var nextCounter = operationMetadata.addressCounter + 1 31 | var operationsWithCounter: [OperationWithCounter] = [] 32 | for operation in mutableOperations { 33 | let operationWithCounter = OperationWithCounter(operation: operation, counter: nextCounter) 34 | operationsWithCounter.append(operationWithCounter) 35 | nextCounter += 1 36 | } 37 | 38 | return OperationPayload(operations: operationsWithCounter, operationMetadata: operationMetadata) 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /Tests/UnitTests/TezosKit/AbstractOperationTest.swift: -------------------------------------------------------------------------------- 1 | // Copyright Keefer Taylor, 2018 2 | 3 | @testable import TezosKit 4 | import XCTest 5 | 6 | class AbstractOperationTest: XCTestCase { 7 | public func testRequiresReveal() { 8 | let abstractOperationRequiringReveal = AbstractOperation( 9 | source: "tz1abc", 10 | kind: .delegation, 11 | operationFees: OperationFees.testFees 12 | ) 13 | XCTAssertTrue(abstractOperationRequiringReveal.requiresReveal) 14 | 15 | let abstractOperationNotRequiringReveal = AbstractOperation( 16 | source: "tz1abc", 17 | kind: .reveal, 18 | operationFees: OperationFees.testFees 19 | ) 20 | XCTAssertFalse(abstractOperationNotRequiringReveal.requiresReveal) 21 | } 22 | 23 | public func testDictionaryRepresentation() { 24 | let source = "tz1abc" 25 | let kind: OperationKind = .delegation 26 | let fee = Tez(1) 27 | let gasLimit = 200 28 | let storageLimit = 300 29 | let operationFees = OperationFees(fee: fee, gasLimit: gasLimit, storageLimit: storageLimit) 30 | 31 | let abstractOperation = AbstractOperation(source: source, kind: kind, operationFees: operationFees) 32 | let dictionary = abstractOperation.dictionaryRepresentation 33 | 34 | XCTAssertNotNil(dictionary["source"]) 35 | XCTAssertEqual(dictionary["source"] as? String, source) 36 | 37 | XCTAssertNotNil(dictionary["kind"]) 38 | XCTAssertEqual(dictionary["kind"] as? String, kind.rawValue) 39 | 40 | XCTAssertNotNil(dictionary["fee"]) 41 | XCTAssertEqual(dictionary["fee"] as? String, fee.rpcRepresentation) 42 | 43 | XCTAssertNotNil(dictionary["gas_limit"]) 44 | XCTAssertEqual(dictionary["gas_limit"] as? String, String(gasLimit)) 45 | 46 | XCTAssertNotNil(dictionary["storage_limit"]) 47 | XCTAssertEqual(dictionary["storage_limit"] as? String, String(storageLimit)) 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /TezosKit/Common/Michelson/NatMichelsonParameter.swift: -------------------------------------------------------------------------------- 1 | // Copyright Keefer Taylor, 2019. 2 | 3 | import BigInt 4 | import Foundation 5 | 6 | /// A representation of an nat parameter in Michelson. 7 | public class NatMichelsonParameter: AbstractMichelsonParameter { 8 | /// Initialize a representation of an integer using an `UInt`. 9 | /// 10 | /// Note that the michelson int type is unbounded while `UInt` values have bounded precision. Consider using the `init(bigUInt:annotations:)` 11 | /// to represent a full range of values. 12 | public convenience init(int: UInt, annotations: [MichelsonAnnotation]? = nil) { 13 | self.init(string: String(int), annotations: annotations) 14 | } 15 | 16 | /// Initialize a representation of an integer using a positive `Decimal`. 17 | /// 18 | /// Note that the michelson int type is unbounded while `Decimal` values have bounded precision. Consider using the `init(bigUInt:annotations:)` 19 | /// to represent a full range of values. 20 | public convenience init?(decimal: Decimal, annotations: [MichelsonAnnotation]? = nil) { 21 | guard decimal > 0 else { 22 | return nil 23 | } 24 | 25 | self.init(string: "\(decimal)", annotations: annotations) 26 | } 27 | 28 | /// Initialize a representation of an nat using an `BigUInt`. 29 | public convenience init(bigInt: BigUInt, annotations: [MichelsonAnnotation]? = nil) { 30 | self.init(string: String(bigInt), annotations: annotations) 31 | } 32 | 33 | /// Internal initializer. 34 | /// 35 | /// - Parameters: 36 | /// - string: A numerical string representing the precision. 37 | /// - annotations: An array of annotations to apply to the parameter. Defaults to no annotations. 38 | private init(string: String, annotations: [MichelsonAnnotation]? = nil) { 39 | super.init(networkRepresentation: [MichelineConstants.int: string], annotations: annotations) 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /Tests/UnitTests/TezosKit/IntegerResponseAdapterTest.swift: -------------------------------------------------------------------------------- 1 | // Copyright Keefer Taylor, 2018 2 | 3 | import TezosKit 4 | import XCTest 5 | 6 | class IntegerResponseAdapterTest: XCTestCase { 7 | let integer = 123 8 | let integerString = "123" 9 | 10 | public func testParseInteger() { 11 | guard let balanceData = integerString.data(using: .utf8), 12 | let parsedInteger = IntegerResponseAdapter.parse(input: balanceData) else { 13 | XCTFail() 14 | return 15 | } 16 | 17 | XCTAssertNotNil(parsedInteger) 18 | XCTAssertEqual(parsedInteger, integer) 19 | } 20 | 21 | // Balances are returned as quoted from the API. Make sure that quotes can be stripped when 22 | // parsing. 23 | public func testParseIntegerWithQuotes() { 24 | guard let balanceData = ("\"" + integerString + "\"").data(using: .utf8), 25 | let parsedInteger = IntegerResponseAdapter.parse(input: balanceData) else { 26 | XCTFail() 27 | return 28 | } 29 | 30 | XCTAssertNotNil(parsedInteger) 31 | XCTAssertEqual(parsedInteger, integer) 32 | } 33 | 34 | // Ensure white space does not mess up parsing. 35 | public func testParseIntegerWithWhitespace() { 36 | guard let balanceData = (" " + integerString + "\n\n\n").data(using: .utf8), 37 | let parsedInteger = IntegerResponseAdapter.parse(input: balanceData) else { 38 | XCTFail() 39 | return 40 | } 41 | 42 | XCTAssertNotNil(parsedInteger) 43 | XCTAssertEqual(parsedInteger, integer) 44 | } 45 | 46 | // Ensure invalid strings cannot be parsed. 47 | public func testParseIntegerWithInvalidInput() { 48 | let invalidIntegerString = "xyz" 49 | guard let invalidIntegerData = invalidIntegerString.data(using: .utf8) else { 50 | XCTFail() 51 | return 52 | } 53 | let parsedInteger = IntegerResponseAdapter.parse(input: invalidIntegerData) 54 | 55 | XCTAssertNil(parsedInteger) 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /Examples/SecureEnclave/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleDisplayName 8 | Tezos HSM 9 | CFBundleExecutable 10 | $(EXECUTABLE_NAME) 11 | CFBundleIdentifier 12 | $(PRODUCT_BUNDLE_IDENTIFIER) 13 | CFBundleInfoDictionaryVersion 14 | 6.0 15 | CFBundleName 16 | $(PRODUCT_NAME) 17 | CFBundlePackageType 18 | APPL 19 | CFBundleShortVersionString 20 | 1.0 21 | CFBundleVersion 22 | 1 23 | LSRequiresIPhoneOS 24 | 25 | NSAppTransportSecurity 26 | 27 | NSAllowsArbitraryLoads 28 | 29 | 30 | NSFaceIDUsageDescription 31 | This app uses Face ID to sign Tezos Transactions 32 | UILaunchStoryboardName 33 | LaunchScreen 34 | UIMainStoryboardFile 35 | Main 36 | UIRequiredDeviceCapabilities 37 | 38 | armv7 39 | 40 | UISupportedInterfaceOrientations 41 | 42 | UIInterfaceOrientationPortrait 43 | UIInterfaceOrientationLandscapeLeft 44 | UIInterfaceOrientationLandscapeRight 45 | 46 | UISupportedInterfaceOrientations~ipad 47 | 48 | UIInterfaceOrientationPortrait 49 | UIInterfaceOrientationPortraitUpsideDown 50 | UIInterfaceOrientationLandscapeLeft 51 | UIInterfaceOrientationLandscapeRight 52 | 53 | 54 | 55 | -------------------------------------------------------------------------------- /Tests/UnitTests/TezosKit/TezResponseAdapterTest.swift: -------------------------------------------------------------------------------- 1 | // Copyright Keefer Taylor, 2018 2 | 3 | import TezosKit 4 | import XCTest 5 | 6 | class TezResponseAdapterTest: XCTestCase { 7 | public func testParseBalance() { 8 | guard let balance = Tez("3500000"), 9 | let balanceData = balance.rpcRepresentation.data(using: .utf8), 10 | let parsedBalance = TezResponseAdapter.parse(input: balanceData) else { 11 | XCTFail() 12 | return 13 | } 14 | 15 | XCTAssertNotNil(parsedBalance) 16 | XCTAssertEqual(parsedBalance, balance) 17 | } 18 | 19 | // Balances are returned as quoted from the API. Make sure that quotes can be stripped when 20 | // parsing. 21 | public func testParseBalanceWithQuotes() { 22 | guard let balance = Tez("3500000"), 23 | let balanceData = ("\"" + balance.rpcRepresentation + "\"").data(using: .utf8), 24 | let parsedBalance = TezResponseAdapter.parse(input: balanceData) else { 25 | XCTFail() 26 | return 27 | } 28 | 29 | XCTAssertNotNil(parsedBalance) 30 | XCTAssertEqual(parsedBalance, balance) 31 | } 32 | 33 | // Ensure white space does not mess up parsing. 34 | public func testParseBalanceWithWhitespace() { 35 | guard let balance = Tez("3500000"), 36 | let balanceData = (" " + balance.rpcRepresentation + "\n\n\n").data(using: .utf8), 37 | let parsedBalance = TezResponseAdapter.parse(input: balanceData) else { 38 | XCTFail() 39 | return 40 | } 41 | 42 | XCTAssertNotNil(parsedBalance) 43 | XCTAssertEqual(parsedBalance, balance) 44 | } 45 | 46 | // Ensure invalid strings cannot be parsed. 47 | public func testParseBalanceWithInvalidInput() { 48 | let invalidBalanceString = "xyz" 49 | guard let invalidBalanceData = invalidBalanceString.data(using: .utf8) else { 50 | XCTFail() 51 | return 52 | } 53 | let parsedBalance = TezResponseAdapter.parse(input: invalidBalanceData) 54 | 55 | XCTAssertNil(parsedBalance) 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /TezosKit/Common/RPC/RPC.swift: -------------------------------------------------------------------------------- 1 | // Copyright Keefer Taylor, 2018 2 | 3 | import Foundation 4 | 5 | ///An abstract RPC class that defines a request and response handler. 6 | /// 7 | /// RPCs have a generic type associated with them, which is the expected type of the decoded bytes received from the 8 | /// network. The given RepsonseAdapter must meet this type. 9 | /// 10 | /// RPCs represent a network request to the Tezos network. RPCs are implicitly considered GET requests by default. If a 11 | /// payload is defined, then the RPC should be interpreted as a POST. This schema is represented in the derived 12 | /// `isPOSTRequest` property. 13 | /// 14 | /// Concrete subclasses should construct an endpoint and payload and inform this class by calling `super.init`. 15 | public class RPC { 16 | public let endpoint: String 17 | public let headers: [Header] 18 | public let payload: String? 19 | public let responseAdapterClass: AbstractResponseAdapter.Type 20 | public var isPOSTRequest: Bool { 21 | if payload != nil { 22 | return true 23 | } 24 | return false 25 | } 26 | 27 | /// Initialize a new request. 28 | /// 29 | /// By default, requests are considered to be GET requests with an empty body. If payload is set the request should be 30 | /// interpreted as a POST request with the given payload. 31 | /// 32 | /// - Parameters: 33 | /// - endpoint: The endpoint to which the request is being made. 34 | /// - headers: A dictionary of headers to use, default is empty headers. 35 | /// - responseAdapterClass: The class of the response adapter which will take bytes received from the request and 36 | /// transform them into a specific type. 37 | /// - payload: A payload that should be sent with a POST request. 38 | public init( 39 | endpoint: String, 40 | headers: [Header] = [], 41 | responseAdapterClass: AbstractResponseAdapter.Type, 42 | payload: String? = nil 43 | ) { 44 | self.endpoint = endpoint 45 | self.headers = headers 46 | self.responseAdapterClass = responseAdapterClass 47 | self.payload = payload 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /TezosKit/TezosNode/Models/Operation/OriginationOperation.swift: -------------------------------------------------------------------------------- 1 | // 2 | // OriginationOperation.swift 3 | // TezosKit 4 | // 5 | // Created by Simon Mcloughlin on 28/02/2020. 6 | // 7 | 8 | import Foundation 9 | 10 | /// An operation to transact XTZ between addresses. 11 | public class OriginationOperation: AbstractOperation { 12 | private enum JSON { 13 | public enum Keys { 14 | public static let balance = "balance" 15 | public static let code = "code" 16 | public static let storage = "storage" 17 | public static let script = "script" 18 | } 19 | } 20 | 21 | internal let balance: Tez 22 | internal let code: MichelsonParameter 23 | internal let storage: MichelsonParameter 24 | 25 | public override var dictionaryRepresentation: [String: Any] { 26 | var operation = super.dictionaryRepresentation 27 | operation[JSON.Keys.balance] = balance.rpcRepresentation 28 | operation[JSON.Keys.script] = [ 29 | JSON.Keys.code: code.networkRepresentation, 30 | JSON.Keys.storage: storage.networkRepresentation 31 | ] 32 | 33 | return operation 34 | } 35 | 36 | /// - Parameters: 37 | /// - source: The address originating the contract 38 | /// - balance: The amount of XTZ to move to the new contract. 39 | /// - code: Michelson parameters which make up code for the contract 40 | /// - storage: Initial storage for the contract 41 | /// - operationFees: OperationFees for the transaction. 42 | public init( 43 | source: Address, 44 | balance: Tez, 45 | code: MichelsonParameter, 46 | storage: MichelsonParameter, 47 | operationFees: OperationFees 48 | ) { 49 | self.balance = balance 50 | self.code = code 51 | self.storage = storage 52 | 53 | super.init(source: source, kind: .origination, operationFees: operationFees) 54 | } 55 | 56 | public override func mutableCopy(with zone: NSZone? = nil) -> Any { 57 | return OriginationOperation( 58 | source: source, 59 | balance: balance, 60 | code: code, 61 | storage: storage, 62 | operationFees: operationFees 63 | ) 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /Tests/UnitTests/TezosKit/PeriodKindResponseAdapterTest.swift: -------------------------------------------------------------------------------- 1 | // Copyright Keefer Taylor, 2018 2 | 3 | import TezosKit 4 | import XCTest 5 | 6 | class PeriodKindResponseAdapterTest: XCTestCase { 7 | public func testParsePeriodKind() { 8 | guard let proposalData = "proposal".data(using: .utf8), 9 | let testingData = "testing".data(using: .utf8), 10 | let testingVoteData = "testing_vote".data(using: .utf8), 11 | let promotionVoteData = "promotion_vote".data(using: .utf8), 12 | let proposal = PeriodKindResponseAdapter.parse(input: proposalData), 13 | let testing = PeriodKindResponseAdapter.parse(input: testingData), 14 | let testingVote = PeriodKindResponseAdapter.parse(input: testingVoteData), 15 | let promotionVote = PeriodKindResponseAdapter.parse(input: promotionVoteData) else { 16 | XCTFail() 17 | return 18 | } 19 | 20 | XCTAssertEqual(proposal, .proposal) 21 | XCTAssertEqual(testing, .testing) 22 | XCTAssertEqual(testingVote, .testingVote) 23 | XCTAssertEqual(promotionVote, .promotionVote) 24 | } 25 | 26 | // Ensure quotes are stripped properly 27 | public func testParsePeriodKindWithQuotes() { 28 | guard let proposalData = "\"proposal\"".data(using: .utf8), 29 | let proposal = PeriodKindResponseAdapter.parse(input: proposalData) else { 30 | XCTFail() 31 | return 32 | } 33 | XCTAssertEqual(proposal, .proposal) 34 | } 35 | 36 | // Ensure whitespace are stripped properly 37 | public func testParsePeriodKindWithWhitespace() { 38 | guard let proposalData = "\nproposal ".data(using: .utf8), 39 | let proposal = PeriodKindResponseAdapter.parse(input: proposalData) else { 40 | XCTFail() 41 | return 42 | } 43 | XCTAssertEqual(proposal, .proposal) 44 | } 45 | 46 | // Ensure invalid strings cannot be parsed. 47 | public func testParsPeriodKindWithInvalidInput() { 48 | guard let invalidData = "not_valid".data(using: .utf8) else { 49 | XCTFail() 50 | return 51 | } 52 | 53 | let proposal = PeriodKindResponseAdapter.parse(input: invalidData) 54 | XCTAssertNil(proposal) 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /project.yml: -------------------------------------------------------------------------------- 1 | name: TezosKit 2 | options: 3 | bundleIdPrefix: com.keefertaylor 4 | settings: 5 | LD_RUNPATH_SEARCH_PATHS: "$(inherited) @executable_path/../Frameworks @loader_path/../Frameworks $(PROJECT_DIR)/Carthage/Build/iOS" 6 | targets: 7 | TezosKit_iOS: 8 | type: framework 9 | platform: iOS 10 | sources: [TezosKit, Extensions] 11 | deploymentTarget: 10.0 12 | scheme: 13 | testTargets: 14 | - TezosKitTests_iOS 15 | gatherCoverageData: true 16 | postCompileScripts: 17 | - script: swiftlint autocorrect --config .swiftlint.yml 18 | name: SwiftLint 19 | settings: 20 | base: 21 | PRODUCT_NAME: TezosKit 22 | dependencies: 23 | - carthage: BigInt 24 | - carthage: DTTJailbreakDetection 25 | - carthage: Sodium 26 | - carthage: CryptoSwift 27 | - carthage: PromiseKit 28 | - carthage: Base58Swift 29 | - carthage: MnemonicKit 30 | - carthage: secp256k1 31 | TezosKit_macOS: 32 | type: framework 33 | platform: macOS 34 | sources: [TezosKit, Extensions] 35 | scheme: 36 | testTargets: 37 | - TezosKitTests_macOS 38 | gatherCoverageData: true 39 | postCompileScripts: 40 | - script: swiftlint autocorrect --config .swiftlint.yml 41 | name: SwiftLint 42 | settings: 43 | base: 44 | PRODUCT_NAME: TezosKit 45 | dependencies: 46 | - carthage: BigInt 47 | - carthage: Sodium 48 | - carthage: CryptoSwift 49 | - carthage: PromiseKit 50 | - carthage: Base58Swift 51 | - carthage: MnemonicKit 52 | - carthage: secp256k1 53 | TezosKitTests: 54 | type: bundle.unit-test 55 | platform: [iOS, macOS] 56 | sources: [Tests/UnitTests, Tests/Common] 57 | dependencies: 58 | - target: TezosKit_${platform} 59 | TezosKitIntegrationTests: 60 | type: bundle.unit-test 61 | platform: [iOS, macOS] 62 | sources: [Tests/IntegrationTests, Tests/Common] 63 | dependencies: 64 | - target: TezosKit_${platform} 65 | SecureEnclaveExample: 66 | type: application 67 | platform: iOS 68 | deploymentTarget: "10.0" 69 | sources: [Examples/SecureEnclave] 70 | dependencies: 71 | - target: TezosKit_iOS 72 | -------------------------------------------------------------------------------- /Tests/IntegrationTests/Extensions/PromiseKit/ConseilClientIntegrationTests+Promises.swift: -------------------------------------------------------------------------------- 1 | // Copyright Keefer Taylor, 2019 2 | 3 | import Foundation 4 | import PromiseKit 5 | import TezosKit 6 | import XCTest 7 | 8 | /// Integration tests for the ConseilClient Promises Extension. 9 | /// Please see instructions in header of `ConseilClientIntegrationTests.swift`. 10 | extension ConseilClientIntegrationTests { 11 | func testGetTransactionsReceived_promises() { 12 | let expectation = XCTestExpectation(description: "promise fulfilled") 13 | 14 | conseilClient.transactionsReceived(from: Wallet.testWallet.address).done { result in 15 | XCTAssertNotNil(result) 16 | XCTAssert(result.count > 1) 17 | expectation.fulfill() 18 | } .catch { _ in 19 | XCTFail() 20 | } 21 | 22 | wait(for: [expectation], timeout: .expectationTimeout) 23 | } 24 | 25 | func testGetTransactionsSent_promises() { 26 | let expectation = XCTestExpectation(description: "promise fulfilled") 27 | 28 | conseilClient.transactionsSent(from: Wallet.testWallet.address).done { result in 29 | XCTAssertNotNil(result) 30 | XCTAssert(result.count > 1) 31 | expectation.fulfill() 32 | } .catch { _ in 33 | XCTFail() 34 | } 35 | 36 | wait(for: [expectation], timeout: .expectationTimeout) 37 | } 38 | 39 | func testGetTransactions_promises() { 40 | let expectation = XCTestExpectation(description: "promise fulfilled") 41 | 42 | conseilClient.transactions(from: Wallet.testWallet.address).done { result in 43 | XCTAssertNotNil(result) 44 | XCTAssert(result.count > 1) 45 | expectation.fulfill() 46 | } .catch { _ in 47 | XCTFail() 48 | } 49 | 50 | wait(for: [expectation], timeout: .expectationTimeout) 51 | } 52 | 53 | func testGetOriginatedContracts_promises() { 54 | let expectation = XCTestExpectation(description: "promise fulfilled") 55 | conseilClient.originatedContracts(from: Wallet.testWallet.address).done { result in 56 | XCTAssertNotNil(result) 57 | XCTAssert(result.count > 1) 58 | expectation.fulfill() 59 | } .catch { error in 60 | XCTFail("\(error)") 61 | } 62 | 63 | wait(for: [expectation], timeout: .expectationTimeout) 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /Tests/UnitTests/TezosKit/DelegationOperationTest.swift: -------------------------------------------------------------------------------- 1 | // Copyright Keefer Taylor, 2018 2 | 3 | import TezosKit 4 | import XCTest 5 | 6 | class DelegationOperationTest: XCTestCase { 7 | public func testDictionaryRepresentation_delegate() { 8 | let source = "tz1abc" 9 | let delegate = "tz1def" 10 | 11 | guard case let .success(operation) = OperationFactory.testFactory.delegateOperation( 12 | source: source, 13 | to: delegate, 14 | operationFeePolicy: .default, 15 | signatureProvider: FakeSignatureProvider.testSignatureProvider 16 | ) else { 17 | XCTFail() 18 | return 19 | } 20 | let dictionary = operation.dictionaryRepresentation 21 | 22 | XCTAssertNotNil(dictionary["source"]) 23 | XCTAssertEqual(dictionary["source"] as? String, source) 24 | 25 | XCTAssertNotNil(dictionary["delegate"]) 26 | XCTAssertEqual(dictionary["delegate"] as? String, delegate) 27 | } 28 | 29 | public func testDictionaryRepresentation_undelegate() { 30 | let source = "tz1abc" 31 | 32 | guard case let .success(operation) = OperationFactory.testFactory.undelegateOperation( 33 | source: source, 34 | operationFeePolicy: .default, 35 | signatureProvider: FakeSignatureProvider.testSignatureProvider 36 | ) else { 37 | XCTFail() 38 | return 39 | } 40 | let dictionary = operation.dictionaryRepresentation 41 | 42 | XCTAssertNotNil(dictionary["source"]) 43 | XCTAssertEqual(dictionary["source"] as? String, source) 44 | 45 | XCTAssertNil(dictionary["delegate"]) 46 | } 47 | 48 | public func testDictionaryRepresentation_registerDelegate() { 49 | let source = "tz1abc" 50 | 51 | guard case let .success(operation) = OperationFactory.testFactory.registerDelegateOperation( 52 | source: source, 53 | operationFeePolicy: .default, 54 | signatureProvider: FakeSignatureProvider.testSignatureProvider 55 | ) else { 56 | XCTFail() 57 | return 58 | } 59 | let dictionary = operation.dictionaryRepresentation 60 | 61 | XCTAssertNotNil(dictionary["source"]) 62 | XCTAssertEqual(dictionary["source"] as? String, source) 63 | 64 | XCTAssertNotNil(dictionary["delegate"]) 65 | XCTAssertEqual(dictionary["delegate"] as? String, source) 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /Examples/SecureEnclave/SecureEnclaveAppDelegate.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.swift 3 | // Enclave 4 | // 5 | // Created by Keefer Taylor on 5/1/19. 6 | // Copyright © 2019 Keefer Taylor. All rights reserved. 7 | // 8 | import TezosKit 9 | import UIKit 10 | 11 | @UIApplicationMain 12 | class AppDelegate: UIResponder, UIApplicationDelegate { 13 | 14 | var window: UIWindow? 15 | var nc: TezosNodeClient? 16 | 17 | func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { 18 | 19 | // Override point for customization after application launch. 20 | return true 21 | } 22 | 23 | func applicationWillResignActive(_ application: UIApplication) { 24 | // Sent when the application is about to move from active to inactive state. This can occur for certain types of temporary interruptions (such as an incoming phone call or SMS message) or when the user quits the application and it begins the transition to the background state. 25 | // Use this method to pause ongoing tasks, disable timers, and invalidate graphics rendering callbacks. Games should use this method to pause the game. 26 | } 27 | 28 | func applicationDidEnterBackground(_ application: UIApplication) { 29 | // Use this method to release shared resources, save user data, invalidate timers, and store enough application state information to restore your application to its current state in case it is terminated later. 30 | // If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits. 31 | } 32 | 33 | func applicationWillEnterForeground(_ application: UIApplication) { 34 | // Called as part of the transition from the background to the active state; here you can undo many of the changes made on entering the background. 35 | } 36 | 37 | func applicationDidBecomeActive(_ application: UIApplication) { 38 | // Restart any tasks that were paused (or not yet started) while the application was inactive. If the application was previously in the background, optionally refresh the user interface. 39 | } 40 | 41 | func applicationWillTerminate(_ application: UIApplication) { 42 | // Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:. 43 | } 44 | 45 | } 46 | -------------------------------------------------------------------------------- /TezosKit/Conseil/Models/ConseilQuery.swift: -------------------------------------------------------------------------------- 1 | // Copyright Keefer Taylor, 2019 2 | 3 | import Foundation 4 | 5 | public typealias ConseilPredicate = [String: Any] 6 | public typealias ConseilOrderBy = [String: Any] 7 | 8 | public enum ConseilQuery: String { 9 | case fields 10 | 11 | case predicates 12 | public enum Predicates: String { 13 | case set 14 | case field 15 | case operation 16 | public enum Operation: String { 17 | case after = "after" 18 | case before 19 | case between 20 | case endsWith 21 | case equal = "eq" 22 | case greaterThan = "gt" 23 | case `in` = "in" 24 | case isNull = "isnull" 25 | case lessThan = "lt" 26 | case like 27 | case startsWith 28 | } 29 | case inverse 30 | 31 | public static func predicateWith( 32 | field: String, 33 | operation: ConseilQuery.Predicates.Operation = .equal, 34 | set: [String] = [], 35 | inverse: Bool = false 36 | ) -> ConseilPredicate { 37 | return [ 38 | ConseilQuery.Predicates.field.rawValue: field, 39 | ConseilQuery.Predicates.operation.rawValue: operation.rawValue, 40 | ConseilQuery.Predicates.inverse.rawValue: inverse, 41 | ConseilQuery.Predicates.set.rawValue: set 42 | ] 43 | } 44 | } 45 | 46 | case aggregation 47 | 48 | case orderBy = "orderby" 49 | public enum OrderBy: String { 50 | case field 51 | case direction 52 | public enum Direction: String { 53 | case ascending = "asc" 54 | case descending = "desc" 55 | } 56 | 57 | public static func orderBy( 58 | field: String, 59 | direction: ConseilQuery.OrderBy.Direction = .descending 60 | ) -> ConseilOrderBy { 61 | return [ 62 | ConseilQuery.OrderBy.field.rawValue: field, 63 | ConseilQuery.OrderBy.direction.rawValue: direction.rawValue 64 | ] 65 | } 66 | } 67 | 68 | case limit 69 | 70 | public static func query( 71 | fields: [String] = [], 72 | predicates: [ConseilPredicate], 73 | orderBy: ConseilOrderBy, 74 | limit: Int 75 | ) -> [String: Any] { 76 | return [ 77 | ConseilQuery.fields.rawValue: fields, 78 | ConseilQuery.predicates.rawValue: predicates, 79 | ConseilQuery.orderBy.rawValue: orderBy, 80 | ConseilQuery.limit.rawValue: limit 81 | ] 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /Tests/UnitTests/TezosKit/TransactionsResponseAdapterTest.swift: -------------------------------------------------------------------------------- 1 | // Copyright Keefer Taylor, 2019 2 | 3 | import TezosKit 4 | import XCTest 5 | 6 | class TransactionsResponseAdapterTest: XCTestCase { 7 | func testParse() { 8 | let source = "tz1abc" 9 | let destination = "tz1xyz" 10 | let amount = Tez(10.0) 11 | let fee = Tez(1) 12 | let timestamp: TimeInterval = 1_234_567 13 | let blockHash = "BMc3kxPnn95TxYKVPehmYWXuaoKBneoPKeDk4sz7usFp7Aumnez" 14 | let blockLevel = 323_100 15 | let operationGroupHash = "opMiJzXJV8nKWy7VTLh2yxFL8yUGDpVkvnbA5hUwj9dSnpMEEMa" 16 | let operationID = 1_511_646 17 | let kind = "transaction" 18 | let status = "applied" 19 | 20 | let validTransaction: [String: Any] = [ 21 | Transaction.JSONKeys.source: source, 22 | Transaction.JSONKeys.destination: destination, 23 | Transaction.JSONKeys.amount: Double(amount.rpcRepresentation)!, 24 | Transaction.JSONKeys.fee: Double(fee.rpcRepresentation)!, 25 | Transaction.JSONKeys.timestamp: timestamp, 26 | Transaction.JSONKeys.blockHash: blockHash, 27 | Transaction.JSONKeys.blockLevel: blockLevel, 28 | Transaction.JSONKeys.operationGroupHash: operationGroupHash, 29 | Transaction.JSONKeys.operationID: operationID, 30 | Transaction.JSONKeys.kind: kind, 31 | Transaction.JSONKeys.status: status 32 | ] 33 | 34 | let transactionWithMissingField: [String: Any] = [ 35 | Transaction.JSONKeys.source: source, 36 | Transaction.JSONKeys.destination: destination, 37 | Transaction.JSONKeys.amount: Double(amount.rpcRepresentation)!, 38 | Transaction.JSONKeys.timestamp: timestamp 39 | ] 40 | 41 | let badInput: [String: Any] = [ 42 | "publicKey": "edpk123xyz", 43 | "kind": "reveal" 44 | ] 45 | 46 | let transactions = [ validTransaction, transactionWithMissingField, badInput ] 47 | let data = JSONUtils.jsonString(for: transactions)?.data(using: .utf8) 48 | 49 | let parsedTransactions = TransactionsResponseAdapter.parse(input: data!)! 50 | XCTAssertEqual(parsedTransactions.count, 1) 51 | XCTAssertEqual(parsedTransactions[0].source, source) 52 | XCTAssertEqual(parsedTransactions[0].destination, destination) 53 | XCTAssertEqual(parsedTransactions[0].amount, amount) 54 | XCTAssertEqual(parsedTransactions[0].fee, fee) 55 | XCTAssertEqual(parsedTransactions[0].timestamp, timestamp) 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /Extensions/PromiseKit/Conseil/ConseilClient+Promises.swift: -------------------------------------------------------------------------------- 1 | // Copyright Keefer Taylor, 2019 2 | 3 | import PromiseKit 4 | 5 | /// Extension for ConseilClient which provides a Promise/PromiseKit based API. 6 | extension ConseilClient { 7 | /// Retrieve originated contracts. 8 | /// 9 | /// - Parameters: 10 | /// - account: The account to query. 11 | /// - limit: The number of contracts to return, defaults to 100. 12 | /// - completion: A completion callback. 13 | public func originatedContracts( 14 | from account: String, 15 | limit: Int = 100 16 | ) -> Promise<[[String: Any]]> { 17 | let rpc = GetOriginatedContractsRPC(account: account, limit: limit) 18 | return networkClient.send(rpc) 19 | } 20 | 21 | /// Retrieve transactions both sent and received from an account. 22 | /// 23 | /// - Parameters: 24 | /// - account: The account to query. 25 | /// - limit: The number of transactions to return, defaults to 100. 26 | /// - completion: A completion callback. 27 | public func transactions( 28 | from account: String, 29 | limit: Int = 100 30 | ) -> Promise<[Transaction]> { 31 | return Promise { seal in 32 | transactions(from: account, limit: limit) { result in 33 | switch result { 34 | case .success(let data): 35 | seal.fulfill(data) 36 | case .failure(let error): 37 | seal.reject(error) 38 | } 39 | } 40 | } 41 | } 42 | 43 | /// Retrieve transactions received from an account. 44 | /// - Parameters: 45 | /// - account: The account to query. 46 | /// - limit: The number of transactions to return, defaults to 100. 47 | public func transactionsReceived( 48 | from account: String, 49 | limit: Int = 100 50 | ) -> Promise<[Transaction]> { 51 | let rpc = GetReceivedTransactionsRPC( 52 | account: account, 53 | limit: limit 54 | ) 55 | return networkClient.send(rpc) 56 | } 57 | 58 | /// Retrieve transactions sent from an account. 59 | /// - Parameters: 60 | /// - account: The account to query. 61 | /// - limit: The number of transactions to return, defaults to 100. 62 | public func transactionsSent( 63 | from account: String, 64 | limit: Int = 100 65 | ) -> Promise<[Transaction]> { 66 | let rpc = GetSentTransactionsRPC( 67 | account: account, 68 | limit: limit 69 | ) 70 | return networkClient.send(rpc) 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /Tests/UnitTests/TezosKit/TransactionTest.swift: -------------------------------------------------------------------------------- 1 | // Copyright Keefer Taylor, 2019 2 | 3 | import TezosKit 4 | import XCTest 5 | 6 | final class TransactionTest: XCTestCase { 7 | func testValidTransaction() { 8 | let source = "tz1abc" 9 | let destination = "tz1xyz" 10 | let amount = Tez(10.0) 11 | let fee = Tez(1) 12 | let timestamp: TimeInterval = 1_234_567 13 | let blockHash = "BMc3kxPnn95TxYKVPehmYWXuaoKBneoPKeDk4sz7usFp7Aumnez" 14 | let blockLevel = 323_100 15 | let operationGroupHash = "opMiJzXJV8nKWy7VTLh2yxFL8yUGDpVkvnbA5hUwj9dSnpMEEMa" 16 | let operationID = 1_511_646 17 | let kind = "transaction" 18 | let status = "applied" 19 | 20 | let jsonDict: [String: Any] = [ 21 | Transaction.JSONKeys.source: source, 22 | Transaction.JSONKeys.destination: destination, 23 | Transaction.JSONKeys.amount: Int(amount.rpcRepresentation)!, 24 | Transaction.JSONKeys.fee: Int(fee.rpcRepresentation)!, 25 | Transaction.JSONKeys.timestamp: timestamp, 26 | Transaction.JSONKeys.blockHash: blockHash, 27 | Transaction.JSONKeys.blockLevel: blockLevel, 28 | Transaction.JSONKeys.operationGroupHash: operationGroupHash, 29 | Transaction.JSONKeys.operationID: operationID, 30 | Transaction.JSONKeys.kind: kind, 31 | Transaction.JSONKeys.status: status 32 | ] 33 | 34 | guard let transaction = Transaction(jsonDict) else { 35 | XCTFail() 36 | return 37 | } 38 | 39 | XCTAssertEqual(transaction.source, source) 40 | XCTAssertEqual(transaction.destination, destination) 41 | XCTAssertEqual(transaction.amount, amount) 42 | XCTAssertEqual(transaction.fee, fee) 43 | XCTAssertEqual(transaction.timestamp, timestamp) 44 | } 45 | 46 | func testInValidTransaction_missingFee() { 47 | let source = "tz1abc" 48 | let destination = "tz1xyz" 49 | let amount = Tez(10.0) 50 | let timestamp: TimeInterval = 1_234_567 51 | 52 | let jsonDict: [String: Any] = [ 53 | Transaction.JSONKeys.source: source, 54 | Transaction.JSONKeys.destination: destination, 55 | Transaction.JSONKeys.amount: Double(amount.humanReadableRepresentation)!, 56 | Transaction.JSONKeys.timestamp: timestamp 57 | ] 58 | 59 | XCTAssertNil(Transaction(jsonDict)) 60 | } 61 | 62 | func testInValidTransaction_badInput() { 63 | let jsonDict: [String: Any] = [ 64 | "publicKey": "edpk123xyz", 65 | "kind": "reveal" 66 | ] 67 | 68 | XCTAssertNil(Transaction(jsonDict)) 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /TezosKit/TezosNode/Models/Operation/SmartContractInvocationOperation.swift: -------------------------------------------------------------------------------- 1 | // Copyright Keefer Taylor, 2020 2 | 3 | import Foundation 4 | 5 | /// An operation to invoke a smart contract. 6 | public class SmartContractInvocationOperation: TransactionOperation { 7 | internal enum JSON { 8 | public enum Keys { 9 | public static let entrypoint = "entrypoint" 10 | public static let parameters = "parameters" 11 | public static let value = "value" 12 | } 13 | public enum Values { 14 | public static let `default` = "default" 15 | } 16 | } 17 | 18 | private let entrypoint: String? 19 | private let parameter: MichelsonParameter? 20 | 21 | public override var dictionaryRepresentation: [String: Any] { 22 | var operation = super.dictionaryRepresentation 23 | 24 | let parameter = self.parameter ?? UnitMichelsonParameter() 25 | let entrypoint = self.entrypoint ?? SmartContractInvocationOperation.JSON.Values.default 26 | 27 | let parameters: [String: Any] = [ 28 | SmartContractInvocationOperation.JSON.Keys.entrypoint: entrypoint, 29 | SmartContractInvocationOperation.JSON.Keys.value: parameter.networkRepresentation 30 | ] 31 | operation[SmartContractInvocationOperation.JSON.Keys.parameters] = parameters 32 | 33 | return operation 34 | } 35 | 36 | /// - Parameters: 37 | /// - amount: The amount of XTZ to transact. 38 | /// - entrypoint: An optional entrypoint to use for the transaction. If nil, the default entry point is used. 39 | /// - parameter: An optional parameter to include in the transaction if the call is being made to a smart contract. If nil, the unit parameter is used. 40 | /// - from: The address that is sending the XTZ. 41 | /// - to: The address that is receiving the XTZ. 42 | /// - operationFees: OperationFees for the transaction. 43 | public init( 44 | amount: Tez, 45 | entrypoint: String? = nil, 46 | parameter: MichelsonParameter? = nil, 47 | source: Address, 48 | destination: Address, 49 | operationFees: OperationFees 50 | ) { 51 | self.entrypoint = entrypoint 52 | self.parameter = parameter 53 | 54 | super.init(amount: amount, source: source, destination: destination, operationFees: operationFees) 55 | } 56 | 57 | public override func mutableCopy(with zone: NSZone? = nil) -> Any { 58 | return SmartContractInvocationOperation( 59 | amount: amount, 60 | entrypoint: entrypoint, 61 | parameter: parameter, 62 | source: source, 63 | destination: destination, 64 | operationFees: operationFees 65 | ) 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /TezosKit/TezosNode/Models/Operation/DelegationOperation.swift: -------------------------------------------------------------------------------- 1 | // Copyright Keefer Taylor, 2018 2 | 3 | import Foundation 4 | 5 | /// An operation to set a delegate for an address. 6 | public class DelegationOperation: AbstractOperation { 7 | // swiftlint:disable weak_delegate 8 | /// The address that will be set as the delegate. 9 | public let delegate: Address? 10 | // swiftlint:enable weak_delegate 11 | 12 | public override var dictionaryRepresentation: [String: Any] { 13 | var operation = super.dictionaryRepresentation 14 | if let delegate = self .delegate { 15 | operation["delegate"] = delegate 16 | } 17 | return operation 18 | } 19 | 20 | /// Create a delegation operation. 21 | /// 22 | /// If delegate and source are the same, then the source will be registered as a delegate. 23 | /// If delegate and source are different, then source will delegate to delegate. 24 | /// If delegate is nil, source will clear any delegation. 25 | /// 26 | /// - Parameters: 27 | /// - source: The address that will delegate funds. 28 | /// - delegate: The address to delegate to. 29 | /// - operationFees: OperationFees for the transaction. 30 | internal init(source: Address, delegate: Address?, operationFees: OperationFees) { 31 | self.delegate = delegate 32 | super.init(source: source, kind: .delegation, operationFees: operationFees) 33 | } 34 | 35 | /// Register the given address as a delegate. 36 | /// - Parameters: 37 | /// - source: The address that will register as a delegate. 38 | /// - operationFees: OperationFees for the transaction. 39 | public static func registerDelegateOperation( 40 | source: Address, 41 | operationFees: OperationFees 42 | ) -> DelegationOperation { 43 | return DelegationOperation(source: source, delegate: source, operationFees: operationFees) 44 | } 45 | 46 | /// Delegate to the given address. 47 | /// - Parameters: 48 | /// - source: The address that will delegate funds. 49 | /// - delegate: The address to delegate to. 50 | /// - operationFees: OperationFees for the transaction. 51 | public static func delegateOperation( 52 | source: Address, 53 | to delegate: Address, 54 | operationFees: OperationFees 55 | ) -> DelegationOperation { 56 | return DelegationOperation(source: source, delegate: delegate, operationFees: operationFees) 57 | } 58 | 59 | /// Clear the delegate from the given address. 60 | /// - Parameters: 61 | /// - source: The address that will have its delegate cleared. 62 | /// - operationFees: OperationFees for the transaction. 63 | public static func undelegateOperation(source: Address, operationFees: OperationFees) -> DelegationOperation { 64 | return DelegationOperation(source: source, delegate: nil, operationFees: operationFees) 65 | } 66 | 67 | public override func mutableCopy(with zone: NSZone? = nil) -> Any { 68 | return DelegationOperation(source: source, delegate: delegate, operationFees: operationFees) 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /Tests/IntegrationTests/TezosKit/ConseilClientIntegrationTests.swift: -------------------------------------------------------------------------------- 1 | // Copyright Keefer Taylor, 2019 2 | 3 | import Foundation 4 | import TezosKit 5 | import XCTest 6 | 7 | /// Integration tests to run against a live Conseil server. 8 | /// 9 | /// To get started using Conseil, look at: 10 | /// https://github.com/Cryptonomic/Conseil/blob/master/doc/use-conseil.md 11 | /// 12 | /// These tests are not hermetic and may fail for a number or reasons, such as: 13 | /// - Adverse network conditions. 14 | /// - Changes in Conseil 15 | /// 16 | /// *** Configuration must be done before theses tests can be run. Please configure: *** 17 | /// - Conseil URL 18 | /// - Conseil API Key 19 | let apiKey = "PUT_KEY_HERE" 20 | let remoteNodeURL = URL(string: "https://conseil-dev.cryptonomic-infra.tech:443")! 21 | 22 | class ConseilClientIntegrationTests: XCTestCase { 23 | public lazy var conseilClient: ConseilClient = { 24 | return ConseilClient(remoteNodeURL: remoteNodeURL, apiKey: apiKey, platform: .tezos, network: .babylonnet) 25 | }() 26 | 27 | public func testConseilSent() { 28 | let expectation = XCTestExpectation(description: "completion called") 29 | conseilClient.transactionsSent(from: Wallet.testWallet.address) { result in 30 | switch result { 31 | case .success(let results): 32 | XCTAssert(results.count > 1) 33 | expectation.fulfill() 34 | case .failure: 35 | XCTFail() 36 | } 37 | } 38 | wait(for: [expectation], timeout: .expectationTimeout) 39 | } 40 | 41 | public func testConseilReceived() { 42 | let expectation = XCTestExpectation(description: "completion called") 43 | conseilClient.transactionsReceived(from: Wallet.testWallet.address) { result in 44 | switch result { 45 | case .success(let results): 46 | XCTAssert(results.count > 1) 47 | expectation.fulfill() 48 | case .failure: 49 | XCTFail() 50 | } 51 | } 52 | wait(for: [expectation], timeout: .expectationTimeout) 53 | } 54 | 55 | public func testConseilTransactions() { 56 | let expectation = XCTestExpectation(description: "completion called") 57 | conseilClient.transactions(from: Wallet.testWallet.address) { result in 58 | switch result { 59 | case .success(let results): 60 | XCTAssert(results.count > 1) 61 | expectation.fulfill() 62 | case .failure: 63 | XCTFail() 64 | } 65 | } 66 | wait(for: [expectation], timeout: .expectationTimeout) 67 | } 68 | 69 | public func testConseilOriginatedContracts() { 70 | let expectation = XCTestExpectation(description: "completion called") 71 | conseilClient.originatedContracts(from: Wallet.testWallet.address) { result in 72 | switch result { 73 | case .success(let results): 74 | XCTAssert(results.count > 1) 75 | expectation.fulfill() 76 | case .failure(let error): 77 | print(error) 78 | XCTFail() 79 | } 80 | } 81 | wait(for: [expectation], timeout: .expectationTimeout) 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /TezosKit/TezosNode/RPC/ResponseAdapters/SimulationResultResponseAdapter.swift: -------------------------------------------------------------------------------- 1 | // Copyright Keefer Taylor, 2019. 2 | 3 | import Foundation 4 | 5 | /// JSON keys and values used in the PreapplicationService. 6 | private enum JSON { 7 | public enum Keys { 8 | public static let consumedGas = "consumed_gas" 9 | public static let contents = "contents" 10 | public static let internalOperationResult = "internal_operation_results" 11 | public static let metadata = "metadata" 12 | public static let operationResult = "operation_result" 13 | public static let result = "result" 14 | public static let status = "status" 15 | public static let storageSize = "storage_size" 16 | } 17 | 18 | public enum Values { 19 | public static let failed = "failed" 20 | } 21 | } 22 | 23 | /// Parse the resulting JSON from a simulation operation to a SimulationResult enum 24 | public class SimulationResultResponseAdapter: AbstractResponseAdapter { 25 | public override class func parse(input: Data) -> SimulationResult? { 26 | guard 27 | let json = JSONDictionaryResponseAdapter.parse(input: input) 28 | else { 29 | return nil 30 | } 31 | 32 | guard 33 | let contents = json[JSON.Keys.contents] as? [[ String: Any ]] 34 | else { 35 | return nil 36 | } 37 | 38 | var consumedGas = 0 39 | var consumedStorage = 0 40 | for content in contents { 41 | guard 42 | let metadata = content[JSON.Keys.metadata] as? [String: Any], 43 | let operationResult = metadata[JSON.Keys.operationResult] as? [String: Any], 44 | let status = operationResult[JSON.Keys.status] as? String 45 | else { 46 | continue 47 | } 48 | 49 | if status == JSON.Values.failed { 50 | return nil 51 | } 52 | 53 | let rawConsumedGas = operationResult[JSON.Keys.consumedGas] as? String ?? "0" 54 | consumedGas += Int(rawConsumedGas) ?? 0 55 | 56 | let rawConsumedStorage = operationResult[JSON.Keys.storageSize] as? String ?? "0" 57 | consumedStorage += Int(rawConsumedStorage) ?? 0 58 | 59 | if let internalOperationResults = metadata[JSON.Keys.internalOperationResult] as? [[String: Any]] { 60 | for internalOperation in internalOperationResults { 61 | guard let intenalOperationResult = internalOperation[JSON.Keys.result] as? [String: Any] else { 62 | continue 63 | } 64 | 65 | let rawInternalConsumedGas = intenalOperationResult[JSON.Keys.consumedGas] as? String ?? "0" 66 | let internalConsumedGas = Int(rawInternalConsumedGas) ?? 0 67 | consumedGas += internalConsumedGas 68 | 69 | let rawInternalConsumedStorage = intenalOperationResult[JSON.Keys.storageSize] as? String ?? "0" 70 | let internalConsumedStorage = Int(rawInternalConsumedStorage) ?? 0 71 | consumedStorage += internalConsumedStorage 72 | } 73 | } 74 | } 75 | 76 | return SimulationResult(consumedGas: consumedGas, consumedStorage: consumedStorage) 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /Tests/UnitTests/TezosKit/ForgingServiceTests.swift: -------------------------------------------------------------------------------- 1 | // Copyright Keefer Taylor, 2019. 2 | 3 | import TezosKit 4 | import XCTest 5 | 6 | class ForgingServiceTests: XCTestCase { 7 | func testForgingServiceWithRemotePolicySync() { 8 | let forgingService = ForgingService(forgingPolicy: .remote, networkClient: FakeNetworkClient.tezosNodeNetworkClient) 9 | 10 | let result = forgingService.forgeSync( 11 | operationPayload: .testOperationPayload, 12 | operationMetadata: .testOperationMetadata 13 | ) 14 | 15 | guard case let .success(forgingServiceForgeResult) = result else { 16 | XCTFail() 17 | return 18 | } 19 | 20 | XCTAssertEqual(forgingServiceForgeResult, .testForgeResult) 21 | } 22 | 23 | func testForgingServiceWithRemotePolicy() { 24 | let forgingService = ForgingService(forgingPolicy: .remote, networkClient: FakeNetworkClient.tezosNodeNetworkClient) 25 | 26 | let forgeCompletionExpectation = XCTestExpectation(description: "Forge completion called.") 27 | forgingService.forge(operationPayload: .testOperationPayload, operationMetadata: .testOperationMetadata) { result in 28 | switch result { 29 | case .success(let forgingServiceForgeResult): 30 | XCTAssertEqual(forgingServiceForgeResult, .testForgeResult) 31 | case .failure: 32 | XCTFail() 33 | } 34 | forgeCompletionExpectation.fulfill() 35 | } 36 | 37 | wait(for: [forgeCompletionExpectation], timeout: .expectationTimeout) 38 | } 39 | 40 | func testForgingServiceWithLocalPolicy() { 41 | let forgingService = ForgingService(forgingPolicy: .local, networkClient: FakeNetworkClient.tezosNodeNetworkClient) 42 | 43 | let forgeCompletionExpectation = XCTestExpectation(description: "Forge completion called.") 44 | forgingService.forge(operationPayload: .testOperationPayload, operationMetadata: .testOperationMetadata) { result in 45 | switch result { 46 | case .success: 47 | XCTFail() 48 | case .failure(let error): 49 | XCTAssertEqual(error, .localForgingNotSupportedForOperation) 50 | } 51 | forgeCompletionExpectation.fulfill() 52 | } 53 | 54 | wait(for: [forgeCompletionExpectation], timeout: .expectationTimeout) 55 | } 56 | 57 | func testForgingServiceWithLocalWithRemoteFallbackPolicyAndUnforgeableOperation() { 58 | let forgingService = ForgingService( 59 | forgingPolicy: .localWithRemoteFallBack, 60 | networkClient: FakeNetworkClient.tezosNodeNetworkClient 61 | ) 62 | 63 | let forgeCompletionExpectation = XCTestExpectation(description: "Forge completion called.") 64 | 65 | forgingService.forge(operationPayload: .testOperationPayload, operationMetadata: .testOperationMetadata) { result in 66 | switch result { 67 | case .success(let forgingServiceForgeResult): 68 | XCTAssertEqual(forgingServiceForgeResult, .testForgeResult) 69 | case .failure: 70 | XCTFail() 71 | } 72 | forgeCompletionExpectation.fulfill() 73 | } 74 | 75 | wait(for: [forgeCompletionExpectation], timeout: .expectationTimeout) 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /Tests/UnitTests/TezosKit/JSONUtilsTest.swift: -------------------------------------------------------------------------------- 1 | // Copyright Keefer Taylor, 2018 2 | 3 | import TezosKit 4 | import XCTest 5 | 6 | final class JSONUtilsTest: XCTestCase { 7 | func testJSONForString() { 8 | let inputs = [ 9 | "A regular string", // Regular string 10 | "\"A quoted string\"" // String with escape sequences 11 | ] 12 | 13 | // Parallel sorted array to |inputs|. 14 | let expectedOutputs = [ 15 | "\"A regular string\"", 16 | "\"\"A quoted string\"\"" 17 | ] 18 | 19 | for (i, input) in inputs.enumerated() { 20 | let result = JSONUtils.jsonString(for: input) 21 | XCTAssertEqual(result, expectedOutputs[i]) 22 | } 23 | } 24 | 25 | func testJSONForDictionary() { 26 | let inputs: [[String: Any]] = [ 27 | ["key1": "val1", "key2": "val2"], // Normal 28 | ["key1": "\"quotedVal1\"", "\"quotedKey2\"": "val2"], // Quoted Strings 29 | ["key1": "val1", "key2": Int(42)] // Values besides strings 30 | ] 31 | 32 | // Parallel sorted array to |inputs|. 33 | let expectedOutputs = [ 34 | "{\"key1\":\"val1\",\"key2\":\"val2\"}", 35 | "{\"\\\"quotedKey2\\\"\":\"val2\",\"key1\":\"\\\"quotedVal1\\\"\"}", 36 | "{\"key1\":\"val1\",\"key2\":42}" 37 | ] 38 | 39 | for (i, input) in inputs.enumerated() { 40 | // Fail if serialization fails. 41 | guard let result = JSONUtils.jsonString(for: input) else { 42 | XCTFail() 43 | return 44 | } 45 | 46 | XCTAssertEqual(result, expectedOutputs[i]) 47 | } 48 | } 49 | 50 | func testJSONForArray() { 51 | let inputs: [[[String: Any]]] = [ 52 | [["key1": "val1", "key2": "val2"]], // Normal 53 | // Multiple elements 54 | [ 55 | ["dict1key1": "dict1val1", "dict1key2": "dict1val2"], 56 | ["dict2key1": "dict2val1", "dict2key2": "dict2val2"] 57 | ], 58 | [["key1": "\"quotedVal1\"", "\"quotedKey2\"": "val2"]], // Quoted Strings 59 | [["key1": "val1", "key2": Int(42)]] // Values besides strings 60 | ] 61 | 62 | // swiftlint:disable line_length 63 | // Parallel sorted array to |inputs|. 64 | let expectedOutputs = [ 65 | "[{\"key1\":\"val1\",\"key2\":\"val2\"}]", 66 | "[{\"dict1key1\":\"dict1val1\",\"dict1key2\":\"dict1val2\"},{\"dict2key1\":\"dict2val1\",\"dict2key2\":\"dict2val2\"}]", 67 | "[{\"\\\"quotedKey2\\\"\":\"val2\",\"key1\":\"\\\"quotedVal1\\\"\"}]", 68 | "[{\"key1\":\"val1\",\"key2\":42}]" 69 | ] 70 | // swiftlint:enable line_length 71 | 72 | for (i, input) in inputs.enumerated() { 73 | // Fail if serialization fails. 74 | guard let result = JSONUtils.jsonString(for: input) else { 75 | XCTFail() 76 | return 77 | } 78 | 79 | XCTAssertEqual(result, expectedOutputs[i]) 80 | } 81 | } 82 | 83 | func testJSONForInt() { 84 | let input = Int.testAddressCounter 85 | let expectedOutput = "\"" + String(input) + "\"" 86 | 87 | guard let result = JSONUtils.jsonString(for: input) else { 88 | XCTFail() 89 | return 90 | } 91 | 92 | XCTAssertEqual(result, expectedOutput) 93 | } 94 | } 95 | -------------------------------------------------------------------------------- /Tests/UnitTests/TezosKit/MnemonicUtilsTest.swift: -------------------------------------------------------------------------------- 1 | // Copyright Keefer Taylor, 2018 2 | 3 | import TezosKit 4 | import XCTest 5 | 6 | class MnemonicUtilsTest: XCTestCase { 7 | // Mnemonic and Passphrase for tests. 8 | private let mnemonic = 9 | "soccer click number muscle police corn couch bitter gorilla camp camera shove expire praise pill" 10 | private let passphrase = "TezosKitTests" 11 | 12 | // Expected seed strings with / without passphrase. 13 | private let expectedSeedStringNoPassphrase = "cce78b57ed8f4ec6767ed35f3aa41df525a03455e24bcc45a8518f63fbeda772" 14 | private let expectedSeedStringWithPassphrase = "cc90fecd0a596e2cd41798612682395faa2ebfe18945a88c6f01e4bfab17c3e3" 15 | 16 | // Mnemonic generation should always succeed. 17 | public func testGenerateMnemonic() { 18 | let result = MnemonicUtil.generateMnemonic() 19 | XCTAssertNotNil(result) 20 | } 21 | 22 | public func testSeedStringFromMnemonicNoPassphrase() { 23 | guard let result = MnemonicUtil.seedString(from: mnemonic) else { 24 | fatalError() 25 | } 26 | XCTAssertEqual(result, expectedSeedStringNoPassphrase) 27 | } 28 | 29 | public func testSeedStringFromMnemonicEmptyPassphrase() { 30 | guard let result = MnemonicUtil.seedString(from: mnemonic, passphrase: "") else { 31 | fatalError() 32 | } 33 | 34 | // Empty passphrase should be the same as no passphrase. 35 | XCTAssertEqual(result, expectedSeedStringNoPassphrase) 36 | } 37 | 38 | public func testSeedStringFromMnemonicWithPassphrase() { 39 | guard let result = MnemonicUtil.seedString(from: mnemonic, passphrase: passphrase) else { 40 | fatalError() 41 | } 42 | 43 | // Empty passphrase should be the same as no passphrase. 44 | XCTAssertEqual(result, expectedSeedStringWithPassphrase) 45 | } 46 | 47 | public func testValidateMnemonic() { 48 | // Valid mnemonic. 49 | let validMnemonic = 50 | "pear peasant pelican pen pear peasant pelican pen pear peasant pelican pen pear peasant pelican pen" 51 | XCTAssertTrue(MnemonicUtil.validate(mnemonic: validMnemonic)) 52 | 53 | // Invalid mnemonic. 54 | let invalidMnemonic = "slacktivist snacktivity snuggie" 55 | XCTAssertFalse(MnemonicUtil.validate(mnemonic: invalidMnemonic)) 56 | 57 | // Empty string should be invalid. 58 | XCTAssertFalse(MnemonicUtil.validate(mnemonic: "")) 59 | 60 | // Unknown languages don't validate. 61 | let spanishMnemonic = "pera campesina pelican" 62 | XCTAssertFalse(MnemonicUtil.validate(mnemonic: spanishMnemonic)) 63 | 64 | // Mixed cases should be normalized. 65 | let mixedCaseMnemonic = "pear PEASANT PeLiCaN pen" 66 | XCTAssertTrue(MnemonicUtil.validate(mnemonic: mixedCaseMnemonic)) 67 | 68 | // Mixed valid words and invalid words should be invalid. 69 | let mixedLanguageMnemonic = "pear peasant pelican pen 路 级 少 图" 70 | XCTAssertFalse(MnemonicUtil.validate(mnemonic: mixedLanguageMnemonic)) 71 | 72 | // Whitespace padding shouldn't matter. 73 | let whitespacePaddedMnemonic = " pear peasant pelican pen\t\t\n" 74 | XCTAssertTrue(MnemonicUtil.validate(mnemonic: whitespacePaddedMnemonic)) 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /Tests/IntegrationTests/ManagerContractClientIntegrationTest.swift: -------------------------------------------------------------------------------- 1 | // Copyright Keefer Taylor, 2019. 2 | 3 | @testable import TezosKit 4 | import XCTest 5 | 6 | /// Integration tests to run against a Manager.tz Contract. These tests require a live alphanet node. 7 | /// 8 | /// To get an alphanet node running locally, follow instructions here: 9 | /// https://tezos.gitlab.io/alphanet/introduction/howtoget.html 10 | /// 11 | /// These tests are not hermetic and may fail for a number or reasons, such as: 12 | /// - Insufficient balance in account. 13 | /// - Adverse network conditions. 14 | /// 15 | /// Before running the tests, you should make sure that there's sufficient tokens in the owners account (which is 16 | /// tz1XVJ8bZUXs7r5NV8dHvuiBhzECvLRLR3jW) in the token contract at: 17 | /// Token Contract: https://better-call.dev/babylon/KT1VPVdNiWskBEVHF3pWdxyxepj4ZaWTGKgz/script 18 | /// Address: https://babylonnet.tzstats.com/tz1XVJ8bZUXs7r5NV8dHvuiBhzECvLRLR3jW 19 | 20 | extension Address { 21 | public static let managerContractAddress = "KT1VPVdNiWskBEVHF3pWdxyxepj4ZaWTGKgz" 22 | public static let managerContractDelegate = "tz1aNJyLu5HbwjKv1e6msaEQoTPy9tv6d9oE" 23 | } 24 | 25 | class ManagerContractClientIntegrationTests: XCTestCase { 26 | public var nodeClient: TezosNodeClient! 27 | public var managerClient: ManagerContractClient! 28 | 29 | public override func setUp() { 30 | super.setUp() 31 | 32 | let nodeClient = TezosNodeClient(remoteNodeURL: .nodeURL) 33 | managerClient = ManagerContractClient( 34 | contractAddress: .managerContractAddress, 35 | tezosNodeClient: nodeClient 36 | ) 37 | } 38 | 39 | public func testDelegate() { 40 | let completionExpectation = XCTestExpectation(description: "Completion called") 41 | 42 | self.managerClient.delegate( 43 | to: .managerContractDelegate, 44 | signatureProvider: Wallet.testWallet 45 | ) { result in 46 | switch result { 47 | case .success: 48 | completionExpectation.fulfill() 49 | case .failure: 50 | XCTFail() 51 | } 52 | } 53 | 54 | wait(for: [ completionExpectation ], timeout: .expectationTimeout) 55 | } 56 | 57 | public func testUndelegate() { 58 | let completionExpectation = XCTestExpectation(description: "Completion called") 59 | 60 | self.managerClient.undelegate( 61 | signatureProvider: Wallet.testWallet 62 | ) { result in 63 | switch result { 64 | case .success: 65 | completionExpectation.fulfill() 66 | case .failure: 67 | XCTFail() 68 | } 69 | } 70 | 71 | wait(for: [ completionExpectation ], timeout: .expectationTimeout) 72 | } 73 | 74 | public func testTransfer() { 75 | let completionExpectation = XCTestExpectation(description: "Completion called") 76 | 77 | self.managerClient.transfer( 78 | to: Wallet.testWallet.address, 79 | amount: Tez(0.5), 80 | signatureProvider: Wallet.testWallet 81 | ) { result in 82 | switch result { 83 | case .success: 84 | completionExpectation.fulfill() 85 | case .failure: 86 | XCTFail() 87 | } 88 | } 89 | 90 | wait(for: [ completionExpectation ], timeout: .expectationTimeout) 91 | } 92 | } 93 | -------------------------------------------------------------------------------- /docs/Operations.md: -------------------------------------------------------------------------------- 1 | # Operations 2 | 3 | Operations are an advanced topic which most users of TezosKit will not need to know about. 4 | 5 | Any injectable operation in the Tezos blockchain is represented by an object that inherits from `AbstractOperation`, which conforms to the `Operation` protocol. 6 | 7 | ## Supported Operations 8 | 9 | Common operations on Tezos are supported out of the box on TezosKit. These operations include: 10 | * Sending Tezzies 11 | * Calling Smart Contracts 12 | * Delegating the Balance of an Account 13 | * Originating a new Account 14 | 15 | Users of these common operations will never have to work with `Operation` objects directly and can instead call associated methdos on `TezosNodeClient`. 16 | 17 | ## Custom Operations 18 | 19 | Users of TezosKit can create and inject custom operations. Creating a custom operation requires some wiring, but is fairly straightforward. 20 | 21 | ### Creating an Operation 22 | 23 | TezosKit can inject any operation which conforms to the `Operation` protocol. Users can wire up custom operations. In most cases, you'll want to subclass `AbstractOperation` to get baseline functionality. 24 | 25 | The `Operation` protocol requires three properties to be implemented: 26 | * `requiresReveal`: If `true` then TezosKit will automatically add a reveal operation any time this operation is applied to an unrevealed account. 27 | * `defaultFees`: An `OperationFees` object which represents the default fees for the operation. 28 | * `dictionaryRepresentation`: A representation of the dictionary in a `Dictionary`. 29 | 30 | Note that `dictionaryRepresentation` contains only the values represented by the operation. Values like `signature` and `branch` are not required. For instance, a `dictionaryRepresentation` of a `TransactionOperation` looks like: 31 | ```swift 32 | [ 33 | "source": "tz1...", 34 | "destination": "tz1...", 35 | "amount": "123", 36 | "kind": "transaction", 37 | "fee": "1", 38 | "gas_limit": "1", 39 | "storage_limit": "1" 40 | ] 41 | ``` 42 | 43 | ### Injecting an Operation 44 | 45 | `TezosNodeClient` provides a method which lets you inject a custom operation. Injecting a custom operation in the node is easy: 46 | 47 | ```swift 48 | let wallet = Wallet(...) 49 | let tezosNodeClient = TezosNodeClient(...) 50 | let customOperation = MyCustomOperation(...) 51 | 52 | tezosNodeClient.forgeSignPreapplyAndInject( 53 | customOperation, 54 | source: wallet.address, 55 | keys: wallet.keys 56 | ) { result in 57 | // Handle result hash 58 | } 59 | 60 | ``` 61 | 62 | Or if you'd like to inject multiple operations, pass them as an array. Operations are processed in the order they are inserted into the array: 63 | 64 | ```swift 65 | let wallet = Wallet(...) 66 | let tezosNodeClient = TezosNodeClient(...) 67 | let transactionOperation = TransactionOperation(...) 68 | let customOperation = MyCustomOperation(...) 69 | 70 | tezosNodeClient.forgeSignPreapplyAndInject( 71 | [customOperation, transactionOperation], 72 | source: wallet.address, 73 | keys: wallet.keys 74 | ) { result in 75 | // Handle result hash 76 | } 77 | 78 | ``` 79 | 80 | Like all `TezosNodeClient` methods, injection of single and multiple operations is supported with closure based callbacks or with Promises. -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | osx_image: xcode11.3 2 | language: swift 3 | cache: 4 | bundler: true 5 | directories: 6 | - Carthage 7 | env: 8 | secure: ink5jhbtR9aGbiVzcrPP+jBLtYKmZ61uxmXmqwqsyAZip2zbL/tktXdLaeNhUnlJHazZF156rOFe5DFSElFmNYvpSZrXJCVMxwWKB+MJWQ879LUk6W4W65jTvkXKZmMrrUqwkOTNNMq0ycfs5tx+eFJBzHd3Z93OX0oKwb44ZjC1NC05LGJfOlZQs+7IWYKXu0XKpZ/Zo+n4jGxMPEaQCMifz8ac8WVAgsgXs5R3ANrAd6AtBHZqNLu+NzGi5FQ2u7M1EERWo69qAmmHQYKB9aYyERi4cU6KD3XF8B89szR3pH41avfYFs+ZqenF7yAm0dN5UoLBqHA5s+yBQ1kzZbvcCunEU88XjgeRuzwTUfyTq8RIN+BNzaAwSCgu/fNMaFLOZjYAXK+gtVm2elx69maC6N+hKpZblmgUtmaOQyXzY3pVNXdqCqqqzIrqSyvqPuuNSzLxjVFJUW8ycYEfB4Iywcwl74IlxWJ58WpeCgMW3cVE4l4kxw+ZWoVniumpVlp69QtgwHloUosOzmzoIwLjgEm6uGZvr3DIKKXwLZACtq8Oj7uhm8iNV4sU5eS0w+b+VC6gYCxI4yOZHWhwtBG4QtrKlEvXC0Gjy4rIXN0JuKiAHLZKiCr0YuCmks2xXv2INNnJAIBVlJ7URVY4OSY+eqCiWUSobO8n48Zu7gk= 9 | 10 | jobs: 11 | include: 12 | - stage: Build - CocoaPods 13 | install: 14 | - gem install cocoapods 15 | script: 16 | # Optimize CI performance by only building for iOS and skipping testing. 17 | # Sources are provided to compile for MacOS in the Carthage phase below. Tests are also 18 | # run in the Carthage phase below. 19 | - travis_wait 500 pod lib lint --platforms=ios --skip-tests 20 | - stage: Build - Carthage And Code Coverage 21 | install: 22 | # Install Carthage by building from source. This works around a bug with Carthage and PromiseKit which 23 | # is resolved in master, but not in a Carthage release yet. 24 | # Note: When re-enabling 'brew update' likely needs to be run. 25 | # TODO(keefertaylor): Remove this when the next version of Carthage is released. 26 | # 27 | # Issue: https://github.com/Carthage/Carthage/issues/2760 28 | # Fix: https://github.com/Carthage/Carthage/pull/2908 29 | - mkdir -p carthage-build 30 | - cd carthage-build 31 | - git clone https://github.com/Carthage/Carthage && cd Carthage && git checkout f132af8e85eb1da84f5d4378ca0e2cdb3b87f598 32 | - sudo make install 33 | - which carthage 34 | - carthage version 35 | - cd ../.. 36 | - sudo rm -rf carthage-build 37 | 38 | - brew install xcodegen 39 | - gem install slather 40 | script: 41 | # Carthage requires that XCode project files are checked in. Project.yml is 42 | # the canonical definition for the .xcodeproj file. Remove the .xcodeproj file 43 | # and regenerate it. 44 | # See: https://github.com/Carthage/Carthage/issues/2684 45 | - rm -rf TezosKit.xcodeproj 46 | - xcodegen generate 47 | 48 | # Build with Carthage 49 | - travis_wait 500 carthage bootstrap --platform iOS,mac --no-use-binaries --cache-builds 50 | - set -o pipefail && xcodebuild test -scheme TezosKit_macOS -destination 'platform=macOS,arch=x86_64' ONLY_ACTIVE_ARCH=YES | xcpretty 51 | - set -o pipefail && xcodebuild test -scheme TezosKit_iOS -destination 'platform=iOS Simulator,name=iPhone XS,OS=12.2' ONLY_ACTIVE_ARCH=YES | xcpretty 52 | 53 | after_success: 54 | # Generate and Upload Code Coverage 55 | - slather 56 | - bash <(curl -s https://codecov.io/bash) -f ./cobertura.xml 57 | 58 | 59 | -------------------------------------------------------------------------------- /docs/Networking.md: -------------------------------------------------------------------------------- 1 | # Networking and RPCs 2 | 3 | Networking and RPCs is an advanced topic which most users of TezosKit will not have to understand or utilize. 4 | 5 | ## Network Stack 6 | 7 | Internally, TezosKit uses a `URLSession` based network stack. The default session is the `shared` session. Users can inject their own session in the initializer of either `TezosNodeClient` or `ConseilClient`. 8 | 9 | ## Components of Networking 10 | 11 | TezosKit's networking and request / response handling is comprised of three major components: 12 | * RPCs: Encapsulate the request to the node, including the endpoint to request, headers to send, and payload data. 13 | * Response Adapters: Encapsulates parsing the data returned from the API to the expected format 14 | * `NetworkClient`: A class which mediates the interaction between `URLSession`, RPC objects and Response Adapters 15 | 16 | ### `NetworkClient` 17 | 18 | `NetworkClient` is a class which contians the core logic for transcribing `RPC` and `ResponseAdapter` objects into requests made to the network. `NetworkClient` provdes a closure callback style API as well as a [PromiseKit](https://github.com/mxcl/Promisekit) style API. 19 | 20 | A `NetworkClient` is instantiated internally in `ConseilClient` and `TezosClient`. `NetworkClients` are immutable and injected into subcomponents of the clients. 21 | 22 | ### Response Adapters 23 | 24 | `URLSession` returns data from the server. Generally, consumers of an API prefer a more structured representation of that data (either as `dictionary` or some other first class object). 25 | 26 | Response Adapter objects parse `Data` to a requested type. The `ResponseAdapter` protocol defines a generic adapter that has an associated type it will parse to. The protocol defines a single method which transforms `Data` to an optional of the associated type. 27 | 28 | Many types of response adapters are provided by default, but users can create their own as well. 29 | 30 | ### RPCs 31 | 32 | The `RPC` class defines an RPC that will make a request to a node and return a response. An `RPC` is generic in `T`, which is the expected type of a response. 33 | 34 | RPCs are expected to be initialized with the following properties: 35 | * endpoint: The endpoint that the request will be made to on the node. 36 | * headers: Headers that will be sent with the RPC. Note that headers for all RPCs can be set on the Abstract node. Headers set on an RPC will override these headers. 37 | * responseAdapterClass: A `ResponseAdapter` that is generic in the same type of the RPC. This adapter will change the returned data to the requested output. 38 | * payload: A payload to be sent with the request. 39 | 40 | ## Building a Custom RPC 41 | 42 | It's easy to build a custom RPC. 43 | 44 | First, find or create a `ResponseAdapter` object that will coerce `Data` to the preferred output of the RPC. If a custom response adapter is required, users should subclass `AbstractResponseAdapter`. 45 | 46 | Second, subclass `RPC` and initialize the super class with the required functionality. 47 | 48 | Lastly, use a generic send method on a client to send the RPC: 49 | ```swift 50 | let nodeClient = TezosNodeClient() // You can also use ConseilClient. 51 | let myCustomRPC = MyCustomRPC(...) 52 | nodeClient.send(myCustomRPC) { result in 53 | // handle result. 54 | } 55 | ```` 56 | -------------------------------------------------------------------------------- /docs/Testing.md: -------------------------------------------------------------------------------- 1 | # Testing 2 | 3 | TezosKit makes use of both unit tests and integration tests to provide confidence in the reliability of the underlying software. 4 | 5 | ## Unit Tests 6 | 7 | Unit tests are used wherever applicable. All unit tests are hermetic and count towards the code coverage metrics. 8 | 9 | ### Running Unit Tests 10 | 11 | Open `TezosKit.xcodeproj`, select the `TezosKitTests` target from the drop down menu and run the unit tests from the XCTest UI. 12 | 13 | ## Integration Tests 14 | 15 | TezosKit comes bundled with integration tests to ensure that the code works against a live node. These tests are not hermetic. However, they do allow thorough testing of the code in real life scenarios. 16 | 17 | As these tests are non-hermetic, you may find that they flake or fail occasionally due to external factors such as: 18 | - Poor connectivity / network conditions 19 | - Adverse network conditions (i.e. A large amount of bakers are offline) 20 | - Adverse network activity (i.e. All blocks are full) 21 | 22 | ### Running Integration Tests 23 | 24 | Running integration tests requires some setup as a custom environment is needed. 25 | 26 | #### Tezos Node Integration Tests 27 | 28 | Running Tezos Node integration tests requires having a running Tezos Node. The integration tests assume that the live node will be running on Alphanet. 29 | 30 | To get an Alphanet node up and running, follow instructions [here](https://tezos.gitlab.io/alphanet/introduction/howtoget.html). 31 | 32 | The integration tests assume that the test account on Alphanet has a sufficient balance (~100 XTZ) to run the tests. The alphanet test account is [tz1XVJ8bZUXs7r5NV8dHvuiBhzECvLRLR3jW](https://alphanet.tzscan.io/tz1XVJ8bZUXs7r5NV8dHvuiBhzECvLRLR3jW). If the test account needs to be topped up, follow instructions to load additional XTZ from the Alphanet fauce [here](https://tezos.gitlab.io/alphanet/introduction/howtouse.html#faucet). 33 | 34 | These tests also utilize a token and Dexter Exchange contract, located at: 35 | https://alphanet.tzscan.io/KT1RrfbcDM5eqho4j4u5EbqbaoEFwBsXA434 and https://alphanet.tzscan.io/KT1Md4zkfCvkdqgxAC9tyRYpRUBKmD1owEi2. 36 | 37 | (For more information about Dexter, see: https://gitlab.com/camlcase-dev/dexter/blob/master/docs/dexter-cli.md) 38 | 39 | 40 | The integration tests assume that an alphanet node is running at the default location, `http://127.0.0.1:8732`. If this is not true, you may change `nodeUrl` in `TezosNodeIntegrationTests.swift` to point to the correct location. 41 | 42 | With setup complete, open `TezosKit.xcodeproj`, select the `IntegrationTests` target from the drop down menu and run the `TezosNodeIntegrationTests` tests from the XCTest UI. 43 | 44 | #### Conseil Integration Tests 45 | 46 | Running Conseil integration tests requires having a running Conseil service. You can find out more about running your own Conseil service [here](https://github.com/Cryptonomic/Conseil/blob/master/doc/use-conseil.md). 47 | 48 | The integration tests will need to know the location and API Key for the Conseil service it will connect to. Open `Tests/IntegrationTests/Conseil/ConseilIntegrationTests` and set the appropriate values for `remoteNodeURL` and `apiKey` at the top of the file. 49 | 50 | With setup complete, open `TezosKit.xcodeproj`, select the `IntegrationTests` target from the drop down menu and run the `ConseilIntegrationTests` tests from the XCTest UI. 51 | -------------------------------------------------------------------------------- /TezosKit/TezosNode/Models/Operation/OperationResponse.swift: -------------------------------------------------------------------------------- 1 | // 2 | // OperationResponse.swift 3 | // SecureEnclaveExample 4 | // 5 | // Created by Simon Mcloughlin on 17/03/2020. 6 | // 7 | 8 | import Foundation 9 | 10 | /// Codable version of the response object that is returned by the Tezos RPC 11 | struct OperationResponse: Codable { 12 | let contents: [OperationResponseContent] 13 | 14 | /// Check if the operation(s) has been backtracked or reversed due to a failure 15 | func isFailed() -> Bool { 16 | for content in contents { 17 | if content.metadata.operationResult.status == "backtracked" || content.metadata.operationResult.status == "failed" { 18 | return true 19 | } 20 | } 21 | 22 | return false 23 | } 24 | 25 | /// Return the last error object from each internal result. The last error object is the one that contains the location of the error in the smart contract and the `with` string, giving the most debugable information 26 | func errors() -> [OperationResponseInternalResultError] { 27 | var errors: [OperationResponseInternalResultError] = [] 28 | 29 | for content in contents { 30 | if let operationError = content.metadata.operationResult.errors?.last { 31 | errors.append(operationError) 32 | } 33 | 34 | if let internalOperationResults = content.metadata.internalOperationResults { 35 | for internalResult in internalOperationResults { 36 | if let error = internalResult.result.errors?.last { 37 | errors.append(error) 38 | } 39 | } 40 | } 41 | } 42 | 43 | return errors 44 | } 45 | } 46 | 47 | struct OperationResponseContent: Codable { 48 | let kind: String 49 | let source: String 50 | let metadata: OperationResponseMetadata 51 | } 52 | 53 | struct OperationResponseMetadata: Codable { 54 | let operationResult: OperationResponseResult 55 | let internalOperationResults: [OperationResponseInternalOperation]? 56 | 57 | private enum CodingKeys: String, CodingKey { 58 | case operationResult = "operation_result" 59 | case internalOperationResults = "internal_operation_results" 60 | } 61 | } 62 | 63 | struct OperationResponseResult: Codable { 64 | let status: String 65 | let consumedGas: String? 66 | let storageSize: String? 67 | let errors: [OperationResponseInternalResultError]? 68 | 69 | private enum CodingKeys: String, CodingKey { 70 | case status 71 | case consumedGas = "consumed_gas" 72 | case storageSize = "storage_size" 73 | case errors 74 | } 75 | } 76 | 77 | struct OperationResponseInternalOperation: Codable { 78 | let kind: String 79 | let source: String 80 | let result: OperationResponseInternalResult 81 | } 82 | 83 | struct OperationResponseInternalResult: Codable { 84 | let status: String 85 | let errors: [OperationResponseInternalResultError]? 86 | 87 | func isFailed() -> Bool { 88 | return status == "failed" 89 | } 90 | } 91 | 92 | public struct OperationResponseInternalResultError: Codable, Equatable { 93 | public let kind: String 94 | public let id: String 95 | public let location: Int? 96 | public let with: OperationResponseInternalResultErrorWith? 97 | } 98 | 99 | public struct OperationResponseInternalResultErrorWith: Codable, Equatable { 100 | public let string: String 101 | } 102 | -------------------------------------------------------------------------------- /TezosKit/Crypto/CryptoUtils.swift: -------------------------------------------------------------------------------- 1 | // Copyright Keefer Taylor, 2019 2 | 3 | import Base58Swift 4 | import CommonCrypto 5 | import CryptoSwift 6 | import Foundation 7 | import Sodium 8 | 9 | /// A static helper class that provides utility functions for cyptography. 10 | public enum CryptoUtils { 11 | /// Check that a given address is valid public key hash. 12 | public static func validateAddress(address: String) -> Bool { 13 | // Decode bytes. This call verifies the checksum is correct. 14 | guard let decodedBytes = Base58.base58CheckDecode(address) else { 15 | return false 16 | } 17 | 18 | // Check that the prefix is correct. 19 | for (i, byte) in Prefix.Address.tz1.enumerated() where decodedBytes[i] != byte { 20 | return false 21 | } 22 | 23 | return true 24 | } 25 | 26 | /// Convert the given input bytes to hex. 27 | public static func binToHex(_ bin: [UInt8]) -> String? { 28 | return Sodium.shared.utils.bin2hex(bin) 29 | } 30 | 31 | /// Convert the given hex to binary. 32 | public static func hexToBin(_ hex: String) -> [UInt8]? { 33 | return Sodium.shared.utils.hex2bin(hex) 34 | } 35 | 36 | /// Convert signature bytes to their base58 representation. 37 | public static func base58(signature: [UInt8], signingCurve: EllipticalCurve) -> String? { 38 | switch signingCurve { 39 | case .ed25519: 40 | return Base58.encode(message: signature, prefix: Prefix.Keys.Ed25519.signature) 41 | case .secp256k1: 42 | return Base58.encode(message: signature, prefix: Prefix.Keys.Secp256k1.signature) 43 | case .p256: 44 | return Base58.encode(message: signature, prefix: Prefix.Keys.P256.signature) 45 | } 46 | } 47 | 48 | /// Create injectable hex bytes from the given hex operation and signature bytes 49 | public static func injectableHex(_ hex: String, signature: [UInt8]) -> String? { 50 | guard let signatureHex = binToHex(signature) else { 51 | return nil 52 | } 53 | return injectableHex(hex, signatureHex: signatureHex) 54 | } 55 | 56 | /// Create injectable hex bytes from the given hex operation and a hex signature. 57 | public static func injectableHex(_ hex: String, signatureHex: String) -> String { 58 | return hex + signatureHex 59 | } 60 | 61 | /// Compress a 65 byte public key to a 33 byte public key. 62 | /// 63 | /// Tezos expects usage of compressed keys. 64 | public static func compressKey(_ bytes: [UInt8]) -> [UInt8]? { 65 | // A magic byte 0x04 indicates that the key is uncompressed. Compressed keys use 0x02 and 0x03 to indicate the 66 | // key is compressed and the value of the Y coordinate of the keys. 67 | let rawPublicKeyBytes = Array(bytes) 68 | guard 69 | let firstByte = rawPublicKeyBytes.first, 70 | let lastByte = rawPublicKeyBytes.last, 71 | // Expect an uncompressed key to have length = 65 bytes (two 32 byte coordinates, and 1 magic prefix byte) 72 | rawPublicKeyBytes.count == 65, 73 | // Expect the first byte of the public key to be a magic 0x04 byte, indicating an uncompressed key. 74 | firstByte == 4 75 | else { 76 | return nil 77 | } 78 | 79 | // Assign a new magic byte based on the Y coordinate's parity. 80 | // See: https://bitcointalk.org/index.php?topic=644919.0 81 | let magicByte: [UInt8] = lastByte % 2 == 0 ? [2] : [3] 82 | let xCoordinateBytes = rawPublicKeyBytes[1...32] 83 | return magicByte + xCoordinateBytes 84 | } 85 | } 86 | --------------------------------------------------------------------------------