├── github_logo.png
├── .codecov.yml
├── SLPWalletHostTests
├── Assets.xcassets
│ ├── Contents.json
│ └── AppIcon.appiconset
│ │ └── Contents.json
├── ViewController.swift
├── Info.plist
├── Base.lproj
│ ├── Main.storyboard
│ └── LaunchScreen.storyboard
└── AppDelegate.swift
├── .gitmodules
├── SLPWallet.xcodeproj
├── project.xcworkspace
│ ├── contents.xcworkspacedata
│ └── xcshareddata
│ │ └── IDEWorkspaceChecks.plist
├── xcshareddata
│ └── xcschemes
│ │ ├── SLPWalletHostTests.xcscheme
│ │ └── SLPWallet.xcscheme
└── project.pbxproj
├── SLPWallet.xcworkspace
├── xcshareddata
│ ├── WorkspaceSettings.xcsettings
│ └── IDEWorkspaceChecks.plist
└── contents.xcworkspacedata
├── SLPWallet
├── Wallet
│ ├── SLPWalletAccount.swift
│ └── SLPWalletUTXO.swift
├── Extensions
│ ├── UserDefaults+Extensions.swift
│ ├── String+Extensions.swift
│ ├── Double+Extensions.swift
│ ├── Data+Extensions.swift
│ └── Array+Extensions.swift
├── StorageProvider.swift
├── StorageProvider
│ ├── InternalStorageProvider.swift
│ └── SecureStorageProvider.swift
├── SLPWalletConfig.swift
├── Token
│ ├── SLPTokenUTXO.swift
│ └── SLPToken.swift
├── Info.plist
├── Utils
│ ├── TokenQtyConverter.swift
│ ├── SLPTransactionParser.swift
│ └── SLPTransactionBuilder.swift
├── Networks
│ └── RestNetwork.swift
├── Services
│ └── RestService.swift
└── SLPWallet.swift
├── SLPWalletTests
├── SLPWalletTests.entitlements
├── UserDefault+ExtensionsTest.swift
├── String+ExtensionsTest.swift
├── Double+ExtensionsTest.swift
├── Info.plist
├── SLPWalletConfigTest.swift
├── SecureStorageProviderTest.swift
├── InternalStorageProviderTest.swift
├── TokenQtyConverterTest.swift
├── SLPWalletUTXOTest.swift
├── Assets
│ ├── tx_details_genesis_tst.json
│ ├── tx_details_mint_lvl001.json
│ └── tx_details_send_tst.json
├── SLPTokenTest.swift
├── RestServiceTest.swift
├── SLPWalletTest.swift
├── SLPTransactionParserTest.swift
└── SLPTransactionBuilderTest.swift
├── .travis.yml
├── Podfile
├── LICENSE
├── SLPWallet.podspec
├── .gitignore
└── README.md
/github_logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Bitcoin-com/slp-wallet-sdk-ios/HEAD/github_logo.png
--------------------------------------------------------------------------------
/.codecov.yml:
--------------------------------------------------------------------------------
1 | codecov:
2 | branch: master
3 | ignore:
4 | - SLPWalletHostTests/*
5 | - Sample/*
6 | - Pods/*
--------------------------------------------------------------------------------
/SLPWalletHostTests/Assets.xcassets/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "info" : {
3 | "version" : 1,
4 | "author" : "xcode"
5 | }
6 | }
--------------------------------------------------------------------------------
/.gitmodules:
--------------------------------------------------------------------------------
1 | [submodule "Sample/SLPWalletDemo"]
2 | path = Sample/SLPWalletDemo
3 | url = https://github.com/Bitcoin-com/slp-wallet-sdk-ios-demo.git
4 |
--------------------------------------------------------------------------------
/SLPWallet.xcodeproj/project.xcworkspace/contents.xcworkspacedata:
--------------------------------------------------------------------------------
1 |
2 |
4 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/SLPWallet.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/SLPWallet.xcworkspace/contents.xcworkspacedata:
--------------------------------------------------------------------------------
1 |
2 |
4 |
6 |
7 |
9 |
10 |
11 |
--------------------------------------------------------------------------------
/SLPWallet.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | IDEDidComputeMac32BitWarning
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/SLPWallet.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | IDEDidComputeMac32BitWarning
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/SLPWallet/Wallet/SLPWalletAccount.swift:
--------------------------------------------------------------------------------
1 | //
2 | // SLPAccount.swift
3 | // SLPWallet
4 | //
5 | // Created by Jean-Baptiste Dominguez on 2019/03/20.
6 | // Copyright © 2019 Bitcoin.com. All rights reserved.
7 | //
8 |
9 | import BitcoinKit
10 |
11 | struct SLPWalletAccount {
12 | let privKey: PrivateKey
13 | let cashAddress: String
14 | }
15 |
--------------------------------------------------------------------------------
/SLPWalletTests/SLPWalletTests.entitlements:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | keychain-access-groups
6 |
7 | $(AppIdentifierPrefix)com.bitcoin.SLPWalletTests
8 |
9 |
10 |
11 |
--------------------------------------------------------------------------------
/SLPWallet/Extensions/UserDefaults+Extensions.swift:
--------------------------------------------------------------------------------
1 | //
2 | // UserDefaults+Extensions.swift
3 | // SLPSDK
4 | //
5 | // Created by Jean-Baptiste Dominguez on 2019/02/26.
6 | // Copyright © 2019 Bitcoin.com. All rights reserved.
7 | //
8 |
9 | import Foundation
10 |
11 | extension UserDefaults {
12 | public static var SLPWallet: UserDefaults {
13 | return UserDefaults(suiteName: "SLPWallet")!
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/SLPWallet/StorageProvider.swift:
--------------------------------------------------------------------------------
1 | //
2 | // StorageProvider.swift
3 | // SLPWallet
4 | //
5 | // Created by Jean-Baptiste Dominguez on 2019/03/27.
6 | // Copyright © 2019 Bitcoin.com. All rights reserved.
7 | //
8 |
9 | import Foundation
10 |
11 | protocol StorageProvider {
12 | func remove(_ key: String) throws
13 | func setString(_ value: String, key: String) throws
14 | func getString(_ key: String) throws -> String?
15 | }
16 |
--------------------------------------------------------------------------------
/SLPWalletHostTests/ViewController.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ViewController.swift
3 | // SLPWalletHostTests
4 | //
5 | // Created by Jean-Baptiste Dominguez on 2019/03/10.
6 | // Copyright © 2019 Bitcoin.com. All rights reserved.
7 | //
8 |
9 | import UIKit
10 |
11 | class ViewController: UIViewController {
12 |
13 | override func viewDidLoad() {
14 | super.viewDidLoad()
15 | // Do any additional setup after loading the view, typically from a nib.
16 | }
17 |
18 |
19 | }
20 |
21 |
--------------------------------------------------------------------------------
/SLPWallet/Extensions/String+Extensions.swift:
--------------------------------------------------------------------------------
1 | //
2 | // String+Extensions.swift
3 | // SLPWallet
4 | //
5 | // Created by Jean-Baptiste Dominguez on 2019/02/28.
6 | // Copyright © 2019 Bitcoin.com. All rights reserved.
7 | //
8 |
9 | import Foundation
10 |
11 | extension String {
12 | func toSatoshis() -> Int64 {
13 | return self.toDouble().toSatoshis()
14 | }
15 |
16 | func toDouble() -> Double {
17 | return NSDecimalNumber(string: self).doubleValue
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | language: objective-c
2 | osx_image: xcode10.1
3 | before_install:
4 | - travis_retry pod repo update
5 | install:
6 | - gem install cocoapods --pre
7 | - pod deintegrate
8 | - pod cache clean --all
9 | - pod setup
10 | - travis_wait 30 pod install
11 | script:
12 | - xcodebuild -workspace SLPWallet.xcworkspace -scheme SLPWallet -destination platform\=iOS\ Simulator,OS\=12.1,name\=iPhone\ XR -enableCodeCoverage YES test > /dev/null
13 | after_success:
14 | - bash <(curl -s https://codecov.io/bash) -J 'SLPWallet' -t 256b3c8f-ae5a-4958-85b5-d5bbc86ebb83 > /dev/null
--------------------------------------------------------------------------------
/SLPWalletTests/UserDefault+ExtensionsTest.swift:
--------------------------------------------------------------------------------
1 | //
2 | // UserDefault+ExtensionsTest.swift
3 | // SLPWalletTests
4 | //
5 | // Created by Jean-Baptiste Dominguez on 2019/03/12.
6 | // Copyright © 2019 Bitcoin.com. All rights reserved.
7 | //
8 |
9 | import Nimble
10 | import Quick
11 | @testable import SLPWallet
12 |
13 | class UserDefaultTest: QuickSpec {
14 | override func spec() {
15 | describe("UserDefault") {
16 | context("Get SLPWallet") {
17 | it("should success") {
18 | expect(UserDefaults.SLPWallet).toNot(beNil())
19 | }
20 | }
21 | }
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/SLPWallet/Extensions/Double+Extensions.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Double+Extensions.swift
3 | // SLPWallet
4 | //
5 | // Created by Jean-Baptiste Dominguez on 2019/02/28.
6 | // Copyright © 2019 Bitcoin.com. All rights reserved.
7 | //
8 |
9 | import Foundation
10 |
11 | extension Double {
12 | func toSatoshis() -> Int64 {
13 | let double = NSDecimalNumber(value: self).multiplying(by: 100000000).doubleValue
14 | return Int64(double.rounded())
15 | }
16 |
17 | func toString() -> String {
18 | return String(self)
19 | }
20 |
21 | func toInt() -> Int {
22 | return NSDecimalNumber(value: self).intValue
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/SLPWalletTests/String+ExtensionsTest.swift:
--------------------------------------------------------------------------------
1 | //
2 | // String+ExtensionsTest.swift
3 | // SLPWalletTests
4 | //
5 | // Created by Jean-Baptiste Dominguez on 2019/03/12.
6 | // Copyright © 2019 Bitcoin.com. All rights reserved.
7 | //
8 |
9 | import Nimble
10 | import Quick
11 | @testable import SLPWallet
12 |
13 | class StringTest: QuickSpec {
14 | override func spec() {
15 | describe("String") {
16 | context("Converts") {
17 | it("should success") {
18 | let bch: String = "1.2"
19 | expect(bch.toSatoshis()).to(equal(120000000))
20 | }
21 | }
22 | }
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/SLPWallet/StorageProvider/InternalStorageProvider.swift:
--------------------------------------------------------------------------------
1 | //
2 | // UserDefaultStorageProvider.swift
3 | // SLPWallet
4 | //
5 | // Created by Jean-Baptiste Dominguez on 2019/03/27.
6 | // Copyright © 2019 Bitcoin.com. All rights reserved.
7 | //
8 |
9 | import Foundation
10 |
11 | class InternalStorageProvider: StorageProvider {
12 |
13 | func remove(_ key: String) throws {
14 | UserDefaults.SLPWallet.removeObject(forKey: key)
15 | }
16 |
17 | func setString(_ value: String, key: String) throws {
18 | UserDefaults.SLPWallet.set(value, forKey: key)
19 | }
20 |
21 | func getString(_ key: String) throws -> String? {
22 | return UserDefaults.SLPWallet.getString(forKey: key)
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/Podfile:
--------------------------------------------------------------------------------
1 | source 'https://github.com/Bitcoin-com/CocoaPods.git'
2 | source 'https://github.com/CocoaPods/Specs.git'
3 |
4 | platform :ios, '10.0'
5 |
6 | abstract_target 'All' do
7 | use_frameworks!
8 |
9 | # Pods for all targets
10 | pod 'RxSwift', '~> 4.0'
11 | pod 'RxCocoa', '~> 4.0'
12 | pod 'Moya/RxSwift', '~> 11.0'
13 | pod 'KeychainAccess', '~> 3.1.2'
14 | pod 'BitcoinKit', '~> 1.1.1'
15 |
16 | target 'SLPWallet' do
17 | end
18 |
19 | target 'SLPWalletTests' do
20 | inherit! :search_paths
21 |
22 | # Pods for SLPWalletTests
23 | pod 'RxBlocking'
24 | pod 'RxTest'
25 | pod 'Quick'
26 | pod 'Nimble'
27 | end
28 | end
29 |
--------------------------------------------------------------------------------
/SLPWalletTests/Double+ExtensionsTest.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Double+ExtensionsTest.swift
3 | // SLPWalletTests
4 | //
5 | // Created by Jean-Baptiste Dominguez on 2019/03/12.
6 | // Copyright © 2019 Bitcoin.com. All rights reserved.
7 | //
8 |
9 | import Nimble
10 | import Quick
11 | @testable import SLPWallet
12 |
13 | class DoubleTest: QuickSpec {
14 | override func spec() {
15 | describe("Double") {
16 | context("Converts") {
17 | it("should success") {
18 | let bch: Double = 1.2
19 | expect(bch.toInt()).to(equal(1))
20 | expect(bch.toString()).to(equal("1.2"))
21 | expect(bch.toSatoshis()).to(equal(120000000))
22 | }
23 | }
24 | }
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/SLPWallet/SLPWalletConfig.swift:
--------------------------------------------------------------------------------
1 | //
2 | // SLPWalletConfig.swift
3 | // SLPWallet
4 | //
5 | // Created by Jean-Baptiste Dominguez on 2019/03/27.
6 | // Copyright © 2019 Bitcoin.com. All rights reserved.
7 | //
8 |
9 | import Foundation
10 |
11 | public class SLPWalletConfig {
12 |
13 | // Singleton
14 | static var shared = SLPWalletConfig()
15 |
16 | var restAPIKey: String?
17 | var restURL: String = "https://rest.bitcoin.com/v2"
18 |
19 | public static func setRestAPIKey(_ apiKey: String) {
20 | // Any throws for UserDefaults, force wrap is safe
21 | SLPWalletConfig.shared.restAPIKey = apiKey
22 | }
23 |
24 | public static func setRestURL(_ restURL: String) {
25 | // Any throws for UserDefaults, force wrap is safe
26 | SLPWalletConfig.shared.restURL = restURL
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/SLPWallet/Token/SLPTokenUTXO.swift:
--------------------------------------------------------------------------------
1 | //
2 | // SLPTokenUTXO.swift
3 | // SLPWallet
4 | //
5 | // Created by Jean-Baptiste Dominguez on 2019/03/02.
6 | // Copyright © 2019 Bitcoin.com. All rights reserved.
7 | //
8 |
9 | import Foundation
10 |
11 | public class SLPTokenUTXO: SLPWalletUTXO {
12 | var _rawTokenQty: Int
13 | var _tokenQty: Double?
14 | var _isValid: Bool
15 |
16 | public var rawTokenQty: Int { get { return _rawTokenQty } }
17 | public var tokenQty: Double? { get { return _tokenQty } }
18 | public var isValid: Bool { get { return _isValid } }
19 |
20 | public init(_ txid: String, satoshis: Int64, cashAddress: String, scriptPubKey: String, index: Int, rawTokenQty: Int) {
21 | self._rawTokenQty = rawTokenQty
22 | self._isValid = false
23 | super.init(txid, satoshis: satoshis, cashAddress: cashAddress, scriptPubKey: scriptPubKey, index: index)
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/SLPWalletTests/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 | NSAppTransportSecurity
22 |
23 | NSAllowsArbitraryLoads
24 |
25 |
26 |
27 |
28 |
--------------------------------------------------------------------------------
/SLPWallet/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 | 0.1
19 | CFBundleVersion
20 | $(CURRENT_PROJECT_VERSION)
21 | NSAppTransportSecurity
22 |
23 | NSAllowsArbitraryLoads
24 |
25 |
26 |
27 |
28 |
--------------------------------------------------------------------------------
/SLPWallet/StorageProvider/SecureStorageProvider.swift:
--------------------------------------------------------------------------------
1 |
2 | //
3 | // SecureStorageProvider.swift
4 | // SLPWallet
5 | //
6 | // Created by Jean-Baptiste Dominguez on 2019/03/12.
7 | // Copyright © 2019 Bitcoin.com. All rights reserved.
8 | //
9 |
10 | import KeychainAccess
11 |
12 | class SecureStorageProvider: StorageProvider {
13 | fileprivate let keychain: Keychain = {
14 | guard let bundleId = Bundle.main.bundleIdentifier else {
15 | fatalError("Should initialize properly to start using this SDK")
16 | }
17 | return Keychain(service: bundleId)
18 | }()
19 |
20 | func remove(_ key: String) throws {
21 | try keychain.remove(key)
22 | }
23 |
24 | func setString(_ value: String, key: String) throws {
25 | try keychain.set(value, key: key)
26 | }
27 |
28 | func getString(_ key: String) throws -> String? {
29 | return try keychain.get(key)
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2019 Bitcoin com
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/SLPWallet/Extensions/Data+Extensions.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Data+Extensions.swift
3 | // SLPWallet
4 | //
5 | // Created by Jean-Baptiste Dominguez on 2019/02/27.
6 | // Copyright © 2019 Bitcoin.com. All rights reserved.
7 | //
8 |
9 | import Foundation
10 |
11 | public extension Data {
12 |
13 | func removeLeft() -> Data {
14 | var newData = self
15 | newData.removeFirst()
16 | return newData
17 | }
18 |
19 | func removeRight() -> Data {
20 | var newData = self
21 | newData.removeLast()
22 | return newData
23 | }
24 |
25 | var uint8: UInt8 {
26 | get {
27 | var number: UInt8 = 0
28 | self.copyBytes(to:&number, count: MemoryLayout.size)
29 | return number
30 | }
31 | }
32 |
33 | var stringASCII: String? {
34 | get {
35 | return NSString(data: self, encoding: String.Encoding.ascii.rawValue) as String?
36 | }
37 | }
38 |
39 | var stringUTF8: String? {
40 | get {
41 | return NSString(data: self, encoding: String.Encoding.utf8.rawValue) as String?
42 | }
43 | }
44 | }
45 |
--------------------------------------------------------------------------------
/SLPWallet/Extensions/Array+Extensions.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Array+Extensions.swift
3 | // SLPWallet
4 | //
5 | // Created by Jean-Baptiste Dominguez on 2019/03/02.
6 | // Copyright © 2019 Bitcoin.com. All rights reserved.
7 | //
8 |
9 | import Foundation
10 |
11 | public extension Array where Element: Equatable {
12 | func removeDuplicates() -> Array {
13 | var result = [Element]()
14 | for value in self {
15 | if !result.contains(value) {
16 | result.append(value)
17 | }
18 | }
19 | return result
20 | }
21 |
22 | func chunk(_ chunkSize: Int) -> [[Element]] {
23 | return stride(from: 0, to: self.count, by: chunkSize).map { (startIndex) -> [Element] in
24 | let endIndex = (startIndex.advanced(by: chunkSize) > self.count) ? self.count-startIndex : chunkSize
25 | return Array(self[startIndex..(newElements: C) where C.Iterator.Element == Element{
30 | let filteredList = newElements.filter { !self.contains($0) }
31 | self.append(contentsOf: filteredList)
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/SLPWalletTests/SLPWalletConfigTest.swift:
--------------------------------------------------------------------------------
1 | //
2 | // SLPWalletConfigTest.swift
3 | // SLPWalletTests
4 | //
5 | // Created by Jean-Baptiste Dominguez on 2019/03/27.
6 | // Copyright © 2019 Bitcoin.com. All rights reserved.
7 | //
8 |
9 | import Nimble
10 | import Quick
11 | @testable import SLPWallet
12 |
13 | class SLPWalletConfigTest: QuickSpec {
14 | override func spec() {
15 | describe("SLPWalletConfig") {
16 | context("SetAPIKey") {
17 | it("should success") {
18 | expect(SLPWalletConfig.shared.restAPIKey).to(beNil())
19 |
20 | SLPWalletConfig.setRestAPIKey("test")
21 | expect(SLPWalletConfig.shared.restAPIKey).to(equal("test"))
22 | }
23 | }
24 |
25 | context("SetURL") {
26 | it("should success") {
27 | expect(SLPWalletConfig.shared.restURL).to(equal("https://rest.bitcoin.com/v2"))
28 |
29 | SLPWalletConfig.setRestURL("test")
30 | expect(SLPWalletConfig.shared.restURL).to(equal("test"))
31 | }
32 | }
33 | }
34 | }
35 | }
36 |
--------------------------------------------------------------------------------
/SLPWallet/Utils/TokenQtyConverter.swift:
--------------------------------------------------------------------------------
1 | //
2 | // TokenQtyConverter.swift
3 | // SLPWallet
4 | //
5 | // Created by Jean-Baptiste Dominguez on 2019/03/04.
6 | // Copyright © 2019 Bitcoin.com. All rights reserved.
7 | //
8 |
9 | import Foundation
10 |
11 | class TokenQtyConverter {
12 |
13 | static func convertToQty(_ rawAmount: Int, decimal: Int) -> Double {
14 | let amount = decimal > 0 ? Double(rawAmount) / pow(Double(10), Double(decimal)) : Double(rawAmount)
15 | return amount
16 | }
17 |
18 | static func convertToRawQty(_ amount: Double, decimal: Int) -> Int {
19 | let rawAmount = decimal > 0 ? Int(amount * pow(Double(10), Double(decimal))) : Int(amount)
20 | return rawAmount
21 | }
22 |
23 | static func convertToData(_ rawAmount: Int) -> Data? {
24 |
25 | // Convert the amount in hexa
26 | let amountInHex = String(rawAmount, radix: 16)
27 |
28 | // Create the empty hex
29 | var amountInHex16 = [Character](repeating: "0", count: 16)
30 | for (i, value) in amountInHex.enumerated() {
31 | amountInHex16[amountInHex16.count - amountInHex.count + i] = value
32 | }
33 |
34 | return Data(hex: String(amountInHex16))
35 | }
36 | }
37 |
--------------------------------------------------------------------------------
/SLPWalletTests/SecureStorageProviderTest.swift:
--------------------------------------------------------------------------------
1 | //
2 | // SecureStorageProviderTest.swift
3 | // SLPWalletTests
4 | //
5 | // Created by Jean-Baptiste Dominguez on 2019/03/27.
6 | // Copyright © 2019 Bitcoin.com. All rights reserved.
7 | //
8 |
9 | import Nimble
10 | import Quick
11 | @testable import SLPWallet
12 |
13 | class SecureStorageProviderTest: QuickSpec {
14 |
15 | override func spec() {
16 | describe("SecureStorageProvider") {
17 | context("Get/Set/Remove String") {
18 | it("should success") {
19 | let storageProvider = SecureStorageProvider()
20 |
21 | do {
22 | try storageProvider.remove("test")
23 |
24 | var storedValue = try storageProvider.getString("test")
25 | expect(storedValue).to(beNil())
26 |
27 | try storageProvider.setString("value", key: "test")
28 |
29 | storedValue = try storageProvider.getString("test")
30 | expect(storedValue).to(equal("value"))
31 | } catch {
32 | fail()
33 | }
34 |
35 | }
36 | }
37 | }
38 | }
39 | }
40 |
41 |
--------------------------------------------------------------------------------
/SLPWalletTests/InternalStorageProviderTest.swift:
--------------------------------------------------------------------------------
1 | //
2 | // InternalStorageProviderTest.swift
3 | // SLPWalletTests
4 | //
5 | // Created by Jean-Baptiste Dominguez on 2019/03/27.
6 | // Copyright © 2019 Bitcoin.com. All rights reserved.
7 | //
8 |
9 | import Nimble
10 | import Quick
11 | @testable import SLPWallet
12 |
13 | class InternalStorageProviderTest: QuickSpec {
14 |
15 | override func spec() {
16 | describe("InternalStorageProvider") {
17 | context("Get/Set/Remove String") {
18 | it("should success") {
19 | let storageProvider = InternalStorageProvider()
20 |
21 | do {
22 | try storageProvider.remove("test")
23 |
24 | var storedValue = try storageProvider.getString("test")
25 | expect(storedValue).to(beNil())
26 |
27 | try storageProvider.setString("value", key: "test")
28 |
29 | storedValue = try storageProvider.getString("test")
30 | expect(storedValue).to(equal("value"))
31 | } catch {
32 | fail()
33 | }
34 |
35 | }
36 | }
37 | }
38 | }
39 | }
40 |
--------------------------------------------------------------------------------
/SLPWalletTests/TokenQtyConverterTest.swift:
--------------------------------------------------------------------------------
1 | //
2 | // TokenQtyConverterTest.swift
3 | // SLPWalletTests
4 | //
5 | // Created by Jean-Baptiste Dominguez on 2019/03/12.
6 | // Copyright © 2019 Bitcoin.com. All rights reserved.
7 | //
8 |
9 | import Nimble
10 | import Quick
11 | @testable import SLPWallet
12 |
13 | class TokenQtyConverterTest: QuickSpec {
14 | override func spec() {
15 | describe("TokenQtyConverter") {
16 | context("Convert to quantity") {
17 | it("should success") {
18 | expect(TokenQtyConverter.convertToQty(123456, decimal: 3)).to(equal(123.456))
19 | expect(TokenQtyConverter.convertToQty(123456000, decimal: 3)).to(equal(123456))
20 | }
21 | }
22 |
23 | context("Convert to raw quantity") {
24 | it("should success") {
25 | expect(TokenQtyConverter.convertToRawQty(123.456, decimal: 3)).to(equal(123456))
26 | expect(TokenQtyConverter.convertToRawQty(123456, decimal: 3)).to(equal(123456000))
27 | }
28 | }
29 |
30 | context("Convert to data") {
31 | it("should success") {
32 | expect(TokenQtyConverter.convertToData(1152921504606846976)).toNot(beNil())
33 | }
34 | }
35 | }
36 | }
37 | }
38 |
--------------------------------------------------------------------------------
/SLPWallet.podspec:
--------------------------------------------------------------------------------
1 | #
2 | # Be sure to run `pod spec lint SLPWallet.podspec' to ensure this is a
3 | # valid spec and to remove all comments including this before submitting the spec.
4 | #
5 | # To learn more about Podspec attributes see http://docs.cocoapods.org/specification.html
6 | # To see working Podspecs in the CocoaPods repo see https://github.com/CocoaPods/Specs/
7 | #
8 |
9 | Pod::Spec.new do |s|
10 |
11 | s.name = "SLPWallet"
12 | s.version = "0.1.0"
13 | s.summary = "SLP Wallet SDK for iOS"
14 | s.description = "SLP Wallet SDK for iOS"
15 |
16 | s.homepage = "https://github.com/Bitcoin-com/slp-wallet-sdk-ios"
17 |
18 | s.license = { :type => "MIT", :file => "LICENSE" }
19 |
20 | s.author = { "jbdtky" => "jb@bitcoin.com" }
21 |
22 | s.swift_version = "4.0"
23 | s.platform = :ios, "10.0"
24 |
25 | # When using multiple platforms
26 | # s.ios.deployment_target = "5.0"
27 | # s.osx.deployment_target = "10.7"
28 | # s.watchos.deployment_target = "2.0"
29 | # s.tvos.deployment_target = "9.0"
30 |
31 | s.source = { :git => "https://github.com/Bitcoin-com/slp-wallet-sdk-ios.git", :branch => "v#{s.version}" }
32 | s.source_files = "SLPWallet/**/*.swift"
33 |
34 | # s.resource = "icon.png"
35 | # s.resources = "Resources/*.png"
36 |
37 | # s.preserve_paths = "FilesToSave", "MoreFilesToSave"
38 |
39 | s.dependency "RxSwift", "~> 4.0"
40 | s.dependency "RxCocoa", "~> 4.0"
41 | s.dependency "Moya/RxSwift", "~> 11.0"
42 | s.dependency "KeychainAccess", "~> 3.1.2"
43 | s.dependency "BitcoinKit", "~> 1.1.1"
44 |
45 | end
46 |
--------------------------------------------------------------------------------
/SLPWalletHostTests/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 | APPL
17 | CFBundleShortVersionString
18 | 1.0
19 | CFBundleVersion
20 | 1
21 | LSRequiresIPhoneOS
22 |
23 | UILaunchStoryboardName
24 | LaunchScreen
25 | UIMainStoryboardFile
26 | Main
27 | UIRequiredDeviceCapabilities
28 |
29 | armv7
30 |
31 | UISupportedInterfaceOrientations
32 |
33 | UIInterfaceOrientationPortrait
34 | UIInterfaceOrientationLandscapeLeft
35 | UIInterfaceOrientationLandscapeRight
36 |
37 | UISupportedInterfaceOrientations~ipad
38 |
39 | UIInterfaceOrientationPortrait
40 | UIInterfaceOrientationPortraitUpsideDown
41 | UIInterfaceOrientationLandscapeLeft
42 | UIInterfaceOrientationLandscapeRight
43 |
44 |
45 |
46 |
--------------------------------------------------------------------------------
/SLPWalletHostTests/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 |
--------------------------------------------------------------------------------
/SLPWalletHostTests/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 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Xcode
2 | #
3 | # gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore
4 |
5 | ## Build generated
6 | build/
7 | DerivedData/
8 |
9 | ## Various settings
10 | *.pbxuser
11 | !default.pbxuser
12 | *.mode1v3
13 | !default.mode1v3
14 | *.mode2v3
15 | !default.mode2v3
16 | *.perspectivev3
17 | !default.perspectivev3
18 | xcuserdata/
19 |
20 | ## Other
21 | *.moved-aside
22 | *.xccheckout
23 | *.xcscmblueprint
24 |
25 | ## Obj-C/Swift specific
26 | *.hmap
27 | *.ipa
28 | *.dSYM.zip
29 | *.dSYM
30 |
31 | ## Playgrounds
32 | timeline.xctimeline
33 | playground.xcworkspace
34 |
35 | # Swift Package Manager
36 | #
37 | # Add this line if you want to avoid checking in source code from Swift Package Manager dependencies.
38 | # Packages/
39 | # Package.pins
40 | # Package.resolved
41 | .build/
42 |
43 | # CocoaPods
44 | #
45 | # We recommend against adding the Pods directory to your .gitignore. However
46 | # you should judge for yourself, the pros and cons are mentioned at:
47 | # https://guides.cocoapods.org/using/using-cocoapods.html#should-i-check-the-pods-directory-into-source-control
48 | #
49 | Pods/
50 | Podfile.lock
51 |
52 | # Carthage
53 | #
54 | # Add this line if you want to avoid checking in source code from Carthage dependencies.
55 | # Carthage/Checkouts
56 |
57 | Carthage/Build
58 |
59 | # fastlane
60 | #
61 | # It is recommended to not store the screenshots in the git repo. Instead, use fastlane to re-generate the
62 | # screenshots whenever they are needed.
63 | # For more information about the recommended setup visit:
64 | # https://docs.fastlane.tools/best-practices/source-control/#source-control
65 |
66 | fastlane/report.xml
67 | fastlane/Preview.html
68 | fastlane/screenshots/**/*.png
69 | fastlane/test_output
70 | *.coverage.txt
71 |
--------------------------------------------------------------------------------
/SLPWalletHostTests/Assets.xcassets/AppIcon.appiconset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "idiom" : "iphone",
5 | "size" : "20x20",
6 | "scale" : "2x"
7 | },
8 | {
9 | "idiom" : "iphone",
10 | "size" : "20x20",
11 | "scale" : "3x"
12 | },
13 | {
14 | "idiom" : "iphone",
15 | "size" : "29x29",
16 | "scale" : "2x"
17 | },
18 | {
19 | "idiom" : "iphone",
20 | "size" : "29x29",
21 | "scale" : "3x"
22 | },
23 | {
24 | "idiom" : "iphone",
25 | "size" : "40x40",
26 | "scale" : "2x"
27 | },
28 | {
29 | "idiom" : "iphone",
30 | "size" : "40x40",
31 | "scale" : "3x"
32 | },
33 | {
34 | "idiom" : "iphone",
35 | "size" : "60x60",
36 | "scale" : "2x"
37 | },
38 | {
39 | "idiom" : "iphone",
40 | "size" : "60x60",
41 | "scale" : "3x"
42 | },
43 | {
44 | "idiom" : "ipad",
45 | "size" : "20x20",
46 | "scale" : "1x"
47 | },
48 | {
49 | "idiom" : "ipad",
50 | "size" : "20x20",
51 | "scale" : "2x"
52 | },
53 | {
54 | "idiom" : "ipad",
55 | "size" : "29x29",
56 | "scale" : "1x"
57 | },
58 | {
59 | "idiom" : "ipad",
60 | "size" : "29x29",
61 | "scale" : "2x"
62 | },
63 | {
64 | "idiom" : "ipad",
65 | "size" : "40x40",
66 | "scale" : "1x"
67 | },
68 | {
69 | "idiom" : "ipad",
70 | "size" : "40x40",
71 | "scale" : "2x"
72 | },
73 | {
74 | "idiom" : "ipad",
75 | "size" : "76x76",
76 | "scale" : "1x"
77 | },
78 | {
79 | "idiom" : "ipad",
80 | "size" : "76x76",
81 | "scale" : "2x"
82 | },
83 | {
84 | "idiom" : "ipad",
85 | "size" : "83.5x83.5",
86 | "scale" : "2x"
87 | },
88 | {
89 | "idiom" : "ios-marketing",
90 | "size" : "1024x1024",
91 | "scale" : "1x"
92 | }
93 | ],
94 | "info" : {
95 | "version" : 1,
96 | "author" : "xcode"
97 | }
98 | }
--------------------------------------------------------------------------------
/SLPWallet/Wallet/SLPWalletUTXO.swift:
--------------------------------------------------------------------------------
1 | //
2 | // SLPUTXO.swift
3 | // SLPWallet
4 | //
5 | // Created by Jean-Baptiste Dominguez on 2019/03/02.
6 | // Copyright © 2019 Bitcoin.com. All rights reserved.
7 | //
8 |
9 | import Foundation
10 | import BitcoinKit
11 |
12 | public class SLPWalletUTXO {
13 | var _txid: String // TODO: Expose until I fix the TransactionBuilder to provide the right tx directly
14 | fileprivate var _satoshis: Int64
15 | fileprivate var _cashAddress: String
16 | fileprivate var _scriptPubKey: String
17 | fileprivate var _index: Int
18 |
19 | public var txid: String { get { return _txid } }
20 | public var satoshis: Int64 { get { return _satoshis } }
21 | public var cashAddress: String { get { return _cashAddress } }
22 | public var scriptPubKey: String { get { return _scriptPubKey } }
23 | public var index: Int { get { return _index } }
24 |
25 | public init(_ txid: String, satoshis: Int64, cashAddress: String, scriptPubKey: String, index: Int) {
26 | self._txid = txid
27 | self._satoshis = satoshis
28 | self._cashAddress = cashAddress
29 | self._scriptPubKey = scriptPubKey
30 | self._index = index
31 | }
32 | }
33 |
34 | extension SLPWalletUTXO {
35 | func asUnspentTransaction() -> UnspentTransaction {
36 | let transactionOutput = TransactionOutput(value: UInt64(_satoshis), lockingScript: Data(hex: _scriptPubKey)!)
37 | let txid: Data = Data(hex: String(_txid))!
38 | let txHash: Data = Data(txid.reversed())
39 | let transactionOutpoint = TransactionOutPoint(hash: txHash, index: UInt32(_index))
40 | return UnspentTransaction(output: transactionOutput, outpoint: transactionOutpoint)
41 | }
42 | }
43 |
44 | extension SLPWalletUTXO: Equatable {
45 | public static func == (lhs: SLPWalletUTXO, rhs: SLPWalletUTXO) -> Bool {
46 | return lhs.index == rhs.index &&
47 | lhs.txid == rhs.txid
48 | }
49 | }
50 |
51 | extension SLPWalletUTXO: Hashable {
52 | public var hashValue: Int {
53 | return txid.hashValue << 8 | index
54 | }
55 | }
56 |
--------------------------------------------------------------------------------
/SLPWalletTests/SLPWalletUTXOTest.swift:
--------------------------------------------------------------------------------
1 | //
2 | // SLPWalletUTXOTest.swift
3 | // SLPWalletTests
4 | //
5 | // Created by Jean-Baptiste Dominguez on 2019/03/12.
6 | // Copyright © 2019 Bitcoin.com. All rights reserved.
7 | //
8 |
9 | import Nimble
10 | import Quick
11 | @testable import SLPWallet
12 |
13 | class SLPWalletUTXOTest: QuickSpec {
14 | override func spec() {
15 | describe("SLPWalletUTXO") {
16 | context("Create SLPWalletUTXO") {
17 | it("should success") {
18 | let utxo = SLPWalletUTXO("txid", satoshis: 100, cashAddress: "cashAddress", scriptPubKey: "scriptPubKey", index: 1)
19 | expect(utxo.txid).to(equal("txid"))
20 | expect(utxo.satoshis).to(equal(100))
21 | expect(utxo.cashAddress).to(equal("cashAddress"))
22 | expect(utxo.scriptPubKey).to(equal("scriptPubKey"))
23 | expect(utxo.index).to(equal(1))
24 | }
25 | }
26 | context("Equal SLPWalletUTXO") {
27 | it("should be equal") {
28 | let utxo1 = SLPWalletUTXO("txid1", satoshis: 100, cashAddress: "cashAddress", scriptPubKey: "scriptPubKey", index: 1)
29 | let utxo2 = SLPWalletUTXO("txid1", satoshis: 100, cashAddress: "cashAddress", scriptPubKey: "scriptPubKey", index: 1)
30 | expect(utxo1 == utxo2).to(equal(true))
31 | }
32 |
33 | it("should be not equal") {
34 | let utxo1 = SLPWalletUTXO("txid1", satoshis: 100, cashAddress: "cashAddress", scriptPubKey: "scriptPubKey", index: 1)
35 | let utxo2 = SLPWalletUTXO("txid2", satoshis: 100, cashAddress: "cashAddress", scriptPubKey: "scriptPubKey", index: 1)
36 | expect(utxo1 == utxo2).to(equal(false))
37 | }
38 |
39 | it("should be not equal") {
40 | let utxo1 = SLPWalletUTXO("txid1", satoshis: 100, cashAddress: "cashAddress", scriptPubKey: "scriptPubKey", index: 1)
41 | let utxo2 = SLPWalletUTXO("txid1", satoshis: 100, cashAddress: "cashAddress", scriptPubKey: "scriptPubKey", index: 2)
42 | expect(utxo1 == utxo2).to(equal(false))
43 | }
44 | }
45 | }
46 | }
47 | }
48 |
--------------------------------------------------------------------------------
/SLPWalletHostTests/AppDelegate.swift:
--------------------------------------------------------------------------------
1 | //
2 | // AppDelegate.swift
3 | // SLPWalletHostTests
4 | //
5 | // Created by Jean-Baptiste Dominguez on 2019/03/10.
6 | // Copyright © 2019 Bitcoin.com. All rights reserved.
7 | //
8 |
9 | import UIKit
10 |
11 | @UIApplicationMain
12 | class AppDelegate: UIResponder, UIApplicationDelegate {
13 |
14 | var window: UIWindow?
15 |
16 |
17 | func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
18 | // Override point for customization after application launch.
19 | return true
20 | }
21 |
22 | func applicationWillResignActive(_ application: UIApplication) {
23 | // 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.
24 | // Use this method to pause ongoing tasks, disable timers, and invalidate graphics rendering callbacks. Games should use this method to pause the game.
25 | }
26 |
27 | func applicationDidEnterBackground(_ application: UIApplication) {
28 | // 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.
29 | // If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits.
30 | }
31 |
32 | func applicationWillEnterForeground(_ application: UIApplication) {
33 | // 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.
34 | }
35 |
36 | func applicationDidBecomeActive(_ application: UIApplication) {
37 | // 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.
38 | }
39 |
40 | func applicationWillTerminate(_ application: UIApplication) {
41 | // Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:.
42 | }
43 |
44 |
45 | }
46 |
47 |
--------------------------------------------------------------------------------
/SLPWallet/Networks/RestNetwork.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Network.swift
3 | // SLPWallet
4 | //
5 | // Created by Jean-Baptiste Dominguez on 2019/02/26.
6 | // Copyright © 2019 Bitcoin.com. All rights reserved.
7 | //
8 |
9 | import Moya
10 |
11 | enum RestNetwork {
12 | case fetchUTXOs([String])
13 | case fetchTxDetails([String])
14 | case fetchTxValidations([String])
15 | case broadcast(String)
16 | }
17 |
18 | extension RestNetwork: TargetType {
19 |
20 | public var baseURL: URL {
21 | guard let url = URL(string: SLPWalletConfig.shared.restURL) else {
22 | fatalError("should be able to parse this URL")
23 | }
24 |
25 | return url
26 | }
27 |
28 | public var path: String {
29 | switch self {
30 | case .fetchUTXOs:
31 | return "/address/utxo"
32 | case .fetchTxDetails:
33 | return "/transaction/details"
34 | case .fetchTxValidations:
35 | return "/slp/validateTxid"
36 | case .broadcast(let rawTx):
37 | return "/rawtransactions/sendRawTransaction/\(rawTx)"
38 | }
39 | }
40 |
41 | public var method: Moya.Method {
42 | switch self {
43 | case .fetchUTXOs: return .post
44 | case .fetchTxDetails: return .post
45 | case .fetchTxValidations: return .post
46 | case .broadcast: return .get
47 | }
48 | }
49 |
50 | public var sampleData: Data {
51 | return Data()
52 | }
53 |
54 | public var task: Task {
55 | switch self {
56 | case .fetchUTXOs(let addresses):
57 | return .requestParameters(parameters: ["addresses": addresses], encoding: JSONEncoding.default)
58 | case .fetchTxDetails(let txids):
59 | return .requestParameters(parameters: ["txids": txids], encoding: JSONEncoding.default)
60 | case .fetchTxValidations(let txids):
61 | return .requestParameters(parameters: ["txids": txids], encoding: JSONEncoding.default)
62 | default:
63 | return .requestPlain
64 | }
65 | }
66 |
67 | public var headers: [String : String]? {
68 | var headers = ["Content-Type": "application/json"]
69 |
70 | guard let apiKey = SLPWalletConfig.shared.restAPIKey else {
71 | return headers
72 | }
73 |
74 | // Add the API Key
75 | headers["Authorization"] = "Basic \(apiKey)"
76 |
77 | return headers
78 | }
79 | }
80 |
--------------------------------------------------------------------------------
/SLPWalletTests/Assets/tx_details_genesis_tst.json:
--------------------------------------------------------------------------------
1 | {"txid":"9cc1cf24e502554d2d3d09918c27decda2c260762961acd469c5473fbcfe192e","version":1,"locktime":573581,"vin":[{"txid":"30d884ff293570caf7618fe70c11837bd08c6ee685d66f6e1fcbd85758412782","vout":3,"sequence":4294967294,"n":0,"scriptSig":{"hex":"483045022100b625b427753e2383414f191b621fbdd1b9ce9594f91d8a74b45659979071ffc202207be933af6a412f86f0c9435a03f66fbf47cbfd7de1b3b36393e30b4879ceac4741210329d5ffda1250d97614cfd3a5cb1c89d0a255c59584c091915b21b3e64137fe7a","asm":"3045022100b625b427753e2383414f191b621fbdd1b9ce9594f91d8a74b45659979071ffc202207be933af6a412f86f0c9435a03f66fbf47cbfd7de1b3b36393e30b4879ceac47[ALL|FORKID] 0329d5ffda1250d97614cfd3a5cb1c89d0a255c59584c091915b21b3e64137fe7a"},"value":641440,"legacyAddress":"1GiDQv4mH5mRQ339nkPbM9ppoD2L5Sub8E","cashAddress":"bitcoincash:qzk92nt0xdxc9qy3yj53h9rjw8dk0s9cqqucfqpcd6"}],"vout":[{"value":"0.00000000","n":0,"scriptPubKey":{"hex":"6a04534c500001010747454e4553495303545354105472792120537769667420546f6b656e0e6a6240626974636f696e2e636f6d4c000102010208000000174876e800","asm":"OP_RETURN 5262419 1 47454e45534953 5526356 5472792120537769667420546f6b656e 6a6240626974636f696e2e636f6d 0 2 2 000000174876e800"},"spentTxId":null,"spentIndex":null,"spentHeight":null},{"value":"0.00000546","n":1,"scriptPubKey":{"hex":"76a9146a234e78ca9c43f7161614572e41519c5cea082388ac","asm":"OP_DUP OP_HASH160 6a234e78ca9c43f7161614572e41519c5cea0823 OP_EQUALVERIFY OP_CHECKSIG","addresses":["1AgCvUYA7jp9muzaz6BSjFgTaeH969wAEc"],"type":"pubkeyhash"},"spentTxId":"8260679c31a773efaed90d0a74a7f0ee80153bc015788ae2d8aabb8d011ef286","spentIndex":0,"spentHeight":573597},{"value":"0.00000546","n":2,"scriptPubKey":{"hex":"76a9146a234e78ca9c43f7161614572e41519c5cea082388ac","asm":"OP_DUP OP_HASH160 6a234e78ca9c43f7161614572e41519c5cea0823 OP_EQUALVERIFY OP_CHECKSIG","addresses":["1AgCvUYA7jp9muzaz6BSjFgTaeH969wAEc"],"type":"pubkeyhash"},"spentTxId":null,"spentIndex":null,"spentHeight":null},{"value":"0.00640012","n":3,"scriptPubKey":{"hex":"76a914ac554d6f334d82809124a91b947271db67c0b80088ac","asm":"OP_DUP OP_HASH160 ac554d6f334d82809124a91b947271db67c0b800 OP_EQUALVERIFY OP_CHECKSIG","addresses":["1GiDQv4mH5mRQ339nkPbM9ppoD2L5Sub8E"],"type":"pubkeyhash"},"spentTxId":"337f747d6b8d5af84990b2a19e817765ca8796ac6616535bb95f3c285bbbccf8","spentIndex":2,"spentHeight":573593}],"blockhash":"0000000000000000056bbad37777fd51a6bf0ad204e43010e044b019a6a93e7f","blockheight":573582,"confirmations":1706,"time":1552474594,"blocktime":1552474594,"valueOut":0.00641104,"size":336,"valueIn":0.0064144,"fees":0.00000336}
2 |
--------------------------------------------------------------------------------
/SLPWalletTests/Assets/tx_details_mint_lvl001.json:
--------------------------------------------------------------------------------
1 | {"txid":"c3b72361cee1a7ed5d0911714da7439313eaf22fde842f871656e4d438eba7d1","version":2,"locktime":0,"vin":[{"txid":"218e7c3acc75b9053dd59890b759bb6a80255cc5a776f396903d7467cb0516a4","vout":0,"sequence":4294967295,"n":0,"scriptSig":{"hex":"47304402204e189d7b3150e248a1d12d43f2270d1082fffcd16ad15f22f827af0d7f2c6c95022027b8dd61540019f428fa9d4f909d63a42fa9600dcbabeef244d6fa3674629370412103d0fa8422e080b6a45db0eab75ae3b3e89b390190657d4852a2e6622ee7435eba","asm":"304402204e189d7b3150e248a1d12d43f2270d1082fffcd16ad15f22f827af0d7f2c6c95022027b8dd61540019f428fa9d4f909d63a42fa9600dcbabeef244d6fa3674629370[ALL|FORKID] 03d0fa8422e080b6a45db0eab75ae3b3e89b390190657d4852a2e6622ee7435eba"},"value":1000,"legacyAddress":"19rr6Noynu23KdVU5nodRJHaHyeREm3Xvd","cashAddress":"bitcoincash:qpsjuhn3qdhwtk27q38zrc296ynh89sx05n0y0ygas"},{"txid":"4712f1953f364e11f41e6139bee7899e7b3ef53eaed08a19403b793b80129456","vout":2,"sequence":4294967295,"n":1,"scriptSig":{"hex":"47304402200c3f3b49ab99bff38a99c0f0aeb17c269ac5e57a5a352a6db83561d183bbcb2402207e3def849dc967c8ee8f6a17196b2b08e60b93c56b7f3044caa964753ca9f4b741210238ec2a07b7df4b362528768138b0a24500aa626e0805bca27d53574a1cd36dca","asm":"304402200c3f3b49ab99bff38a99c0f0aeb17c269ac5e57a5a352a6db83561d183bbcb2402207e3def849dc967c8ee8f6a17196b2b08e60b93c56b7f3044caa964753ca9f4b7[ALL|FORKID] 0238ec2a07b7df4b362528768138b0a24500aa626e0805bca27d53574a1cd36dca"},"value":546,"legacyAddress":"1L3TpUNL4HtxkVrw87toGdJthb9VvLE5pc","cashAddress":"bitcoincash:qrgwrx7hvd27jqz8q5fmgr4kg5cy76yp05a3prllmr"}],"vout":[{"value":"0.00000000","n":0,"scriptPubKey":{"hex":"6a04534c50000101044d494e5420d5efb237f43a822ede2086bbefca44f1157b7adf2ddeed87c4b294bd136d1d360102080000000000000001","asm":"OP_RETURN 5262419 1 1414416717 d5efb237f43a822ede2086bbefca44f1157b7adf2ddeed87c4b294bd136d1d36 2 0000000000000001"},"spentTxId":null,"spentIndex":null,"spentHeight":null},{"value":"0.00000546","n":1,"scriptPubKey":{"hex":"76a914a133833c086fa0a4f624d46f17a94a7c1220496088ac","asm":"OP_DUP OP_HASH160 a133833c086fa0a4f624d46f17a94a7c12204960 OP_EQUALVERIFY OP_CHECKSIG","addresses":["1FhMW3dRKz14gKWVZ26ax3sTXqrhpQJR1p"],"type":"pubkeyhash"},"spentTxId":"846e5f8791436b3d502e6e87eae5fb6bbde99533a7259c42dab76f0e37c51b27","spentIndex":2,"spentHeight":575019},{"value":"0.00000546","n":2,"scriptPubKey":{"hex":"76a914d0e19bd76355e900470513b40eb645304f68817d88ac","asm":"OP_DUP OP_HASH160 d0e19bd76355e900470513b40eb645304f68817d OP_EQUALVERIFY OP_CHECKSIG","addresses":["1L3TpUNL4HtxkVrw87toGdJthb9VvLE5pc"],"type":"pubkeyhash"},"spentTxId":"396b971bf45ffab5e0545756de4ecf4b7109dca3df6b4207a155e8cff77cce22","spentIndex":1,"spentHeight":575089}],"blockhash":"000000000000000002ef5af4772deb604aed77cb978e403c2465ec71ba5881de","blockheight":575019,"confirmations":283,"time":1553331316,"blocktime":1553331316,"valueOut":0.00001092,"size":438,"valueIn":0.00001546,"fees":0.00000454}
--------------------------------------------------------------------------------
/SLPWalletTests/Assets/tx_details_send_tst.json:
--------------------------------------------------------------------------------
1 | {"txid":"a9f639148662ca6376c3650f3d7e6dffbe9a477cf947499bfcb2c85412331c2e","version":1,"locktime":0,"vin":[{"txid":"3c3366de75c244ba2ccaab3578880784e904d1b65151ca651816a98fe94079a3","vout":2,"sequence":4294967295,"n":0,"scriptSig":{"hex":"4730440220600f4fc21f63214142b3263c0658289d8a52d83753654c4caa1f0803b88af88502201becdda4624d13b29338f46ef830ad405feb042769ab96f9902c8f117ad0bcb341210258fa5858bd9d8f2eca41f6b828cccedde7a316284948773e29a9415d55c750ca","asm":"30440220600f4fc21f63214142b3263c0658289d8a52d83753654c4caa1f0803b88af88502201becdda4624d13b29338f46ef830ad405feb042769ab96f9902c8f117ad0bcb3[ALL|FORKID] 0258fa5858bd9d8f2eca41f6b828cccedde7a316284948773e29a9415d55c750ca"},"value":546,"legacyAddress":"1F7yFGSFeK5Y87aasQEREyHb1A4XtvDZ88","cashAddress":"bitcoincash:qzdwxdtprwxhynpvcqngtupp3tn58smte5z2yfqe0v"},{"txid":"3c3366de75c244ba2ccaab3578880784e904d1b65151ca651816a98fe94079a3","vout":3,"sequence":4294967295,"n":1,"scriptSig":{"hex":"4730440220181362ca6fda6a479fbe18552d83af9621b6c56f0f0c36acc8e2f4a66f1692d1022058591e45a4dd2359a42f085e4268dffd03d41de1da12ef17c07e83566d12a3494121038f2d5d296773570cde7f5db285c9e82a2607bc18ac0eb28e7239c129ac8beed4","asm":"30440220181362ca6fda6a479fbe18552d83af9621b6c56f0f0c36acc8e2f4a66f1692d1022058591e45a4dd2359a42f085e4268dffd03d41de1da12ef17c07e83566d12a349[ALL|FORKID] 038f2d5d296773570cde7f5db285c9e82a2607bc18ac0eb28e7239c129ac8beed4"},"value":194511,"legacyAddress":"12dQET6vKJZnrP8EivYaobdjon5zKyzZc8","cashAddress":"bitcoincash:qqga5ljshfug5g27532d9xc0w55yxdjzwygw62c0nk"}],"vout":[{"value":"0.00000000","n":0,"scriptPubKey":{"hex":"6a04534c500001010453454e44209cc1cf24e502554d2d3d09918c27decda2c260762961acd469c5473fbcfe192e080000000000030cdc080000000000e61a82","asm":"OP_RETURN 5262419 1 1145980243 9cc1cf24e502554d2d3d09918c27decda2c260762961acd469c5473fbcfe192e 0000000000030cdc 0000000000e61a82"},"spentTxId":null,"spentIndex":null,"spentHeight":null},{"value":"0.00000546","n":1,"scriptPubKey":{"hex":"76a9146a234e78ca9c43f7161614572e41519c5cea082388ac","asm":"OP_DUP OP_HASH160 6a234e78ca9c43f7161614572e41519c5cea0823 OP_EQUALVERIFY OP_CHECKSIG","addresses":["1AgCvUYA7jp9muzaz6BSjFgTaeH969wAEc"],"type":"pubkeyhash"},"spentTxId":null,"spentIndex":null,"spentHeight":null},{"value":"0.00000546","n":2,"scriptPubKey":{"hex":"76a9149ae335611b8d724c2cc02685f0218ae743c36bcd88ac","asm":"OP_DUP OP_HASH160 9ae335611b8d724c2cc02685f0218ae743c36bcd OP_EQUALVERIFY OP_CHECKSIG","addresses":["1F7yFGSFeK5Y87aasQEREyHb1A4XtvDZ88"],"type":"pubkeyhash"},"spentTxId":"2a37795de56822b43b769f6546c5a65ec3dd96bcfe5738142f8f150093771137","spentIndex":0,"spentHeight":575214},{"value":"0.00192966","n":3,"scriptPubKey":{"hex":"76a91411da7e50ba788a215ea454d29b0f75284336427188ac","asm":"OP_DUP OP_HASH160 11da7e50ba788a215ea454d29b0f752843364271 OP_EQUALVERIFY OP_CHECKSIG","addresses":["12dQET6vKJZnrP8EivYaobdjon5zKyzZc8"],"type":"pubkeyhash"},"spentTxId":"2a37795de56822b43b769f6546c5a65ec3dd96bcfe5738142f8f150093771137","spentIndex":1,"spentHeight":575214}],"blockhash":"000000000000000000ab606a11f63365cc4d5260083d3a54d0dd09ef6efc2d79","blockheight":575214,"confirmations":85,"time":1553449491,"blocktime":1553449491,"valueOut":0.00194058,"size":479,"valueIn":0.00195057,"fees":0.00000999}
--------------------------------------------------------------------------------
/SLPWallet/Token/SLPToken.swift:
--------------------------------------------------------------------------------
1 | //
2 | // SLPToken.swift
3 | // SLPWallet
4 | //
5 | // Created by Jean-Baptiste Dominguez on 2019/03/02.
6 | // Copyright © 2019 Bitcoin.com. All rights reserved.
7 | //
8 |
9 | import Foundation
10 | import RxSwift
11 |
12 | public class SLPToken {
13 | var _tokenId: String?
14 | var _tokenTicker: String?
15 | var _tokenName: String?
16 | var _mintUTXO: SLPWalletUTXO?
17 | var _decimal: Int? {
18 | willSet {
19 | guard let decimal = newValue else {
20 | return
21 | }
22 |
23 | // If decimal == 0, replace per the rawTokenQty
24 | _utxos.forEach { $0._tokenQty = (decimal > 0 ? (Double($0._rawTokenQty) / pow(Double(10), Double(decimal))) : Double($0.rawTokenQty)) }
25 | }
26 | }
27 |
28 | var _utxos = [SLPTokenUTXO]() {
29 | willSet {
30 | guard let decimal = self.decimal else {
31 | return
32 | }
33 |
34 | // If decimal == 0, replace per the rawTokenQty
35 | newValue.forEach { $0._tokenQty = (decimal > 0 ? (Double($0._rawTokenQty) / pow(Double(10), Double(decimal))) : Double($0._rawTokenQty)) }
36 | }
37 | }
38 |
39 | // Public interface
40 | public var tokenId: String? { get { return _tokenId } }
41 | public var tokenTicker: String? { get { return _tokenTicker } }
42 | public var tokenName: String? { get { return _tokenName } }
43 | public var mintUTXO: SLPWalletUTXO? { get { return _mintUTXO } }
44 | public var decimal: Int? { get { return _decimal } }
45 | public var utxos: [SLPTokenUTXO] { get { return _utxos.filter { $0.isValid } } }
46 |
47 | public init() {
48 | }
49 |
50 | public init(_ tokenId: String) {
51 | self._tokenId = tokenId
52 | }
53 |
54 | public func getGas() -> Int {
55 | return utxos.reduce(0, { $0 + Int($1.satoshis) })
56 | }
57 |
58 | public func getBalance() -> Double {
59 | return utxos.reduce(0, { $0 + ($1.tokenQty ?? 0) })
60 | }
61 | }
62 |
63 | extension SLPToken {
64 | func addUTXO(_ utxo: SLPTokenUTXO) {
65 | guard let decimal = self.decimal else {
66 | _utxos.append(utxo)
67 | return
68 | }
69 |
70 | utxo._tokenQty = decimal > 0 ? (Double(utxo.rawTokenQty) / pow(Double(10), Double(decimal))) : Double(utxo.rawTokenQty)
71 | _utxos.append(utxo)
72 | }
73 |
74 | func addUTXOs(_ utxos: [SLPTokenUTXO]) {
75 | utxos.forEach { self.addUTXO($0) }
76 | }
77 |
78 | func removeUTXO(_ utxo: SLPTokenUTXO) {
79 | guard let i = _utxos.firstIndex(where: { $0.index == utxo.index && $0.txid == utxo.txid }) else {
80 | return
81 | }
82 | _utxos.remove(at: i)
83 | }
84 |
85 | func merge(_ token: SLPToken) -> SLPToken {
86 | if let tokenId = token._tokenId {
87 | self._tokenId = tokenId
88 | }
89 | if let tokenName = token._tokenName {
90 | self._tokenName = tokenName
91 | }
92 | if let tokenTicker = token._tokenTicker {
93 | self._tokenTicker = tokenTicker
94 | }
95 | if let decimal = token._decimal {
96 | self._decimal = decimal
97 | }
98 | if let mintUTXO = token._mintUTXO {
99 | self._mintUTXO = mintUTXO
100 | }
101 | self._utxos.append(contentsOf: token._utxos)
102 |
103 | return self
104 | }
105 | }
106 |
--------------------------------------------------------------------------------
/SLPWallet.xcodeproj/xcshareddata/xcschemes/SLPWalletHostTests.xcscheme:
--------------------------------------------------------------------------------
1 |
2 |
5 |
8 |
9 |
15 |
21 |
22 |
23 |
24 |
25 |
30 |
31 |
32 |
33 |
39 |
40 |
41 |
42 |
43 |
44 |
54 |
56 |
62 |
63 |
64 |
65 |
66 |
67 |
73 |
75 |
81 |
82 |
83 |
84 |
86 |
87 |
90 |
91 |
92 |
--------------------------------------------------------------------------------
/SLPWalletTests/SLPTokenTest.swift:
--------------------------------------------------------------------------------
1 | //
2 | // SLPTokenTest.swift
3 | // SLPWalletTests
4 | //
5 | // Created by Jean-Baptiste Dominguez on 2019/03/12.
6 | // Copyright © 2019 Bitcoin.com. All rights reserved.
7 | //
8 |
9 | import Nimble
10 | import Quick
11 | @testable import SLPWallet
12 |
13 | class SLPWalletTokenTest: QuickSpec {
14 | override func spec() {
15 | describe("SLPWalletToken") {
16 | context("Add UTXOs") {
17 | it("should success") {
18 | let token = SLPToken("test")
19 |
20 | let utxo = SLPTokenUTXO("txid", satoshis: 100, cashAddress: "cashAddress", scriptPubKey: "scriptPubKey", index: 1, rawTokenQty: 10)
21 | token.addUTXOs([utxo])
22 |
23 | expect(token._utxos.count).to(equal(1))
24 | }
25 | }
26 |
27 | context("Add UTXO") {
28 | it("should success") {
29 | let token = SLPToken("test")
30 |
31 | let utxo = SLPTokenUTXO("txid", satoshis: 100, cashAddress: "cashAddress", scriptPubKey: "scriptPubKey", index: 1, rawTokenQty: 10)
32 | token.addUTXO(utxo)
33 |
34 | expect(token._utxos.count).to(equal(1))
35 | }
36 | }
37 |
38 | context("Get balance without decimal") {
39 | it("should success") {
40 | let token = SLPToken("test")
41 |
42 | let utxo = SLPTokenUTXO("txid", satoshis: 100, cashAddress: "cashAddress", scriptPubKey: "scriptPubKey", index: 1, rawTokenQty: 10)
43 | utxo._isValid = true
44 |
45 | token.addUTXO(utxo)
46 |
47 | expect(token.getBalance()).to(equal(0))
48 | }
49 | }
50 |
51 | context("Get balance with decimal nil") {
52 | it("should success") {
53 | let token = SLPToken("test")
54 | token._decimal = nil
55 |
56 | let utxo = SLPTokenUTXO("txid", satoshis: 100, cashAddress: "cashAddress", scriptPubKey: "scriptPubKey", index: 1, rawTokenQty: 10)
57 | utxo._isValid = true
58 |
59 | token.addUTXO(utxo)
60 |
61 | expect(token.getBalance()).to(equal(0))
62 | }
63 | }
64 |
65 | context("Get balance with decimal 2") {
66 | it("should success") {
67 | let token = SLPToken("test")
68 | token._decimal = 2
69 |
70 | let utxo = SLPTokenUTXO("txid", satoshis: 100, cashAddress: "cashAddress", scriptPubKey: "scriptPubKey", index: 1, rawTokenQty: 10)
71 | utxo._isValid = true
72 |
73 | token.addUTXO(utxo)
74 |
75 | expect(token.getBalance()).to(equal(0.1))
76 | }
77 | }
78 |
79 | context("Get balance with decimal 0") {
80 | it("should success") {
81 | let token = SLPToken("test")
82 | token._decimal = 0
83 |
84 | let utxo = SLPTokenUTXO("txid", satoshis: 100, cashAddress: "cashAddress", scriptPubKey: "scriptPubKey", index: 1, rawTokenQty: 10)
85 | utxo._isValid = true
86 |
87 | token.addUTXO(utxo)
88 |
89 | expect(token.getBalance()).to(equal(10))
90 | }
91 | }
92 | }
93 | }
94 | }
95 |
--------------------------------------------------------------------------------
/SLPWallet.xcodeproj/xcshareddata/xcschemes/SLPWallet.xcscheme:
--------------------------------------------------------------------------------
1 |
2 |
5 |
8 |
9 |
15 |
21 |
22 |
23 |
24 |
25 |
32 |
33 |
39 |
40 |
41 |
42 |
44 |
50 |
51 |
52 |
53 |
54 |
60 |
61 |
62 |
63 |
64 |
65 |
75 |
76 |
82 |
83 |
84 |
85 |
86 |
87 |
93 |
94 |
100 |
101 |
102 |
103 |
105 |
106 |
109 |
110 |
111 |
--------------------------------------------------------------------------------
/SLPWalletTests/RestServiceTest.swift:
--------------------------------------------------------------------------------
1 | //
2 | // RestServiceTest.swift
3 | // SLPWalletTests
4 | //
5 | // Created by Jean-Baptiste Dominguez on 2019/02/27.
6 | // Copyright © 2019 Bitcoin.com. All rights reserved.
7 | //
8 |
9 | import Nimble
10 | import Quick
11 | import RxBlocking
12 | @testable import SLPWallet
13 |
14 | class RestServiceTest: QuickSpec {
15 | override func spec() {
16 | describe("RestService") {
17 |
18 | beforeEach {
19 | SLPWalletConfig.setRestURL("https://rest.bitcoin.com/v2")
20 | }
21 |
22 | context("Fetch UTXO") {
23 | it("should success") {
24 | let utxos = try! RestService
25 | .fetchUTXOs(["bitcoincash:qzk92nt0xdxc9qy3yj53h9rjw8dk0s9cqqucfqpcd6"])
26 | .toBlocking()
27 | .single()
28 | expect(utxos).notTo(beNil())
29 | }
30 | }
31 |
32 | context("Fetch TxDetails") {
33 | it("should success") {
34 | let txs = try! RestService
35 | .fetchTxDetails(["ce7f87ac5d086ad1c736c472ce5bc75f020bf22d3e2ed8603c675a6517b9c1cd"])
36 | .toBlocking()
37 | .single()
38 | expect(txs).notTo(beNil())
39 | }
40 |
41 | it("should fail") {
42 | do {
43 | _ = try RestService
44 | .fetchTxDetails(["test"])
45 | .toBlocking()
46 | .single()
47 | fail()
48 | } catch RestService.RestError.REST_TX_DETAILS {
49 | // Success
50 | } catch {
51 | fail()
52 | }
53 | }
54 | }
55 |
56 | context("Broadcast RawTx") {
57 | it("should fail") {
58 | do {
59 | _ = try RestService
60 | .broadcast("0100000001060f095464b748f3d383b677f0cd5c85807d4b2324412e2759b64706a72f42e3010000006b483045022100c22fb8802b7d539e8143a8b6f71cf4c0d1b496a5846d5f480277bd4360032f8b02204508d9304f5b62d0e29b07a13234cff2f5c1adc54fb34cc2d7207556127e184e41210329d5ffda1250d97614cfd3a5cb1c89d0a255c59584c091915b21b3e64137fe7affffffff040000000000000000406a04534c500001010453454e4420e3422fa70647b659272e4124234b7d80855ccdf077b683d3f348b76454090f060800000000000004b008000000000000002222020000000000001976a914ac554d6f334d82809124a91b947271db67c0b80088ac22020000000000001976a914ac554d6f334d82809124a91b947271db67c0b80088ac85470000000000001976a914ac554d6f334d82809124a91b947271db67c0b80088ac00000000")
61 | .toBlocking()
62 | .single()
63 | fail()
64 | } catch RestService.RestError.REST_SEND_RAW_TX {
65 | // Success
66 | } catch {
67 | fail()
68 | }
69 | }
70 | }
71 |
72 | context("Fetch TxDetails") {
73 | it("should success + valid") {
74 | let txValidations = try! RestService
75 | .fetchTxValidations(["7657b6eb3dbd13ceb0c02a027a44118ede354768689aebd8ebf7007e5a21ae42"])
76 | .toBlocking()
77 | .single()
78 | expect(txValidations).notTo(beNil())
79 | expect(txValidations.count).to(equal(1))
80 | expect(txValidations.first?.txid).to(equal("7657b6eb3dbd13ceb0c02a027a44118ede354768689aebd8ebf7007e5a21ae42"))
81 | expect(txValidations.first?.valid).to(equal(true))
82 | }
83 |
84 | it("should success + invalid") {
85 | let txValidations = try! RestService
86 | .fetchTxValidations(["b42876f55585019f588a39d24a664f8d93fba224e65eef2c1c1979f14069d102"])
87 | .toBlocking()
88 | .single()
89 | expect(txValidations).notTo(beNil())
90 | expect(txValidations.count).to(equal(1))
91 | expect(txValidations.first?.txid).to(equal("b42876f55585019f588a39d24a664f8d93fba224e65eef2c1c1979f14069d102"))
92 | expect(txValidations.first?.valid).to(equal(false))
93 | }
94 |
95 | it("should fail") {
96 | do {
97 | let txValidations = try RestService
98 | .fetchTxValidations(["test"])
99 | .toBlocking()
100 | .single()
101 | fail()
102 | } catch {
103 | // success
104 | }
105 | }
106 | }
107 | }
108 | }
109 | }
110 |
--------------------------------------------------------------------------------
/SLPWallet/Services/RestService.swift:
--------------------------------------------------------------------------------
1 | //
2 | // RestService.swift
3 | // SLPWallet
4 | //
5 | // Created by Jean-Baptiste Dominguez on 2019/02/26.
6 | // Copyright © 2019 Bitcoin.com. All rights reserved.
7 | //
8 |
9 | import Moya
10 | import RxSwift
11 |
12 | public class RestService {
13 | static let bag = DisposeBag()
14 |
15 | enum RestError: String, Error {
16 | case REST_UTXOS = "Failed to fetch UTXOs"
17 | case REST_TX_DETAILS = "Failed to fetch TX details"
18 | case REST_SEND_RAW_TX = "Failed to send TX"
19 | case REST_TX_VALIDATIONS = "Failed to validate TXs"
20 | }
21 | }
22 |
23 | // Fetch UTXOs
24 | //
25 | extension RestService {
26 |
27 | public struct ResponseUTXOs: Codable {
28 | public let utxos: [ResponseUTXO]
29 | public let scriptPubKey: String?
30 | }
31 |
32 | public struct ResponseUTXO: Codable {
33 | public let txid: String
34 | public let vout: Int
35 | public let satoshis: Int
36 | public let confirmations: Int
37 | }
38 |
39 | static public func fetchUTXOs(_ addresses: [String]) -> Single<[ResponseUTXOs]> {
40 |
41 | return Single<[ResponseUTXOs]>.create(subscribe: { (observer) -> Disposable in
42 | // Get a utxo
43 | //
44 | let provider = MoyaProvider()
45 | provider.rx
46 | .request(.fetchUTXOs(addresses))
47 | .retry(3)
48 | .map([ResponseUTXOs].self)
49 | .asObservable()
50 | .subscribe { event in
51 | switch event {
52 | case .next(let utxos):
53 | observer(.success(utxos))
54 | case .error( _):
55 | observer(.error(RestError.REST_UTXOS))
56 | default: break
57 | }
58 | }
59 | .disposed(by: RestService.bag)
60 | return Disposables.create()
61 | })
62 | }
63 | }
64 |
65 | // Fetch TxDetails
66 | //
67 | extension RestService {
68 |
69 | public struct ResponseTx: Codable {
70 | public let txid: String
71 | public let vin: [ResponseInput]
72 | public let vout: [ResponseOutput]
73 | public let confirmations: Int
74 | public let time: Int
75 | public let fees: Double
76 |
77 | public struct ResponseInput: Codable {
78 | public let cashAddress: String
79 | public let value: Int
80 | }
81 |
82 | public struct ResponseOutput: Codable {
83 | public let value: String
84 | public let n: Int
85 | public let scriptPubKey: ResponseScriptPubKey
86 | }
87 |
88 | public struct ResponseScriptPubKey: Codable {
89 | public let addresses: [String]?
90 | public let hex: String
91 | public let asm: String
92 | }
93 | }
94 |
95 | public static func fetchTxDetails(_ txids: [String]) -> Single<[ResponseTx]> {
96 | return Single<[ResponseTx]>.create(subscribe: { (observer) -> Disposable in
97 | // Get tx details
98 | //
99 | let provider = MoyaProvider()
100 | provider.rx
101 | .request(.fetchTxDetails(txids))
102 | .retry(3)
103 | .map([ResponseTx].self)
104 | .asObservable()
105 | .subscribe { event in
106 | switch event {
107 | case .next(let txs):
108 | observer(.success(txs))
109 | case .error( _):
110 | observer(.error(RestError.REST_TX_DETAILS))
111 | default: break
112 | }
113 | }
114 | .disposed(by: RestService.bag)
115 | return Disposables.create()
116 | })
117 | }
118 | }
119 |
120 | // broadcast
121 | //
122 | extension RestService {
123 |
124 | public static func broadcast(_ rawTx: String) -> Single {
125 | return Single.create(subscribe: { (observer) -> Disposable in
126 | let provider = MoyaProvider()
127 | provider.rx
128 | .request(.broadcast(rawTx))
129 | .retry(3)
130 | .asObservable()
131 | .subscribe { event in
132 | switch event {
133 | case .next(let response):
134 | guard let json = try? response.mapJSON()
135 | , let txid = json as? String
136 | , response.statusCode == 200 else {
137 | observer(.error(RestError.REST_SEND_RAW_TX))
138 | return
139 | }
140 | observer(.success(txid))
141 | case .error( _):
142 | observer(.error(RestError.REST_SEND_RAW_TX))
143 | default: break
144 | }
145 | }
146 | .disposed(by: RestService.bag)
147 | return Disposables.create()
148 | })
149 | }
150 | }
151 |
152 | // ValidateTxs
153 | //
154 | extension RestService {
155 |
156 | public struct ResponseTxValidation: Codable {
157 | public let txid: String
158 | public let valid: Bool
159 | }
160 |
161 | public static func fetchTxValidations(_ txIds: [String]) -> Single<[ResponseTxValidation]> {
162 | return Single<[ResponseTxValidation]>.create(subscribe: { (observer) -> Disposable in
163 | let provider = MoyaProvider()
164 | provider.rx
165 | .request(.fetchTxValidations(txIds))
166 | .retry(3)
167 | .map([ResponseTxValidation].self)
168 | .asObservable()
169 | .subscribe { event in
170 | switch event {
171 | case .next(let txValidations):
172 | observer(.success(txValidations))
173 | case .error( _):
174 | observer(.error(RestError.REST_TX_VALIDATIONS))
175 | default: break
176 | }
177 | }
178 | .disposed(by: RestService.bag)
179 | return Disposables.create()
180 | })
181 | }
182 | }
183 |
--------------------------------------------------------------------------------
/SLPWalletTests/SLPWalletTest.swift:
--------------------------------------------------------------------------------
1 | //
2 | // SLPWalletTest.swift
3 | // SLPWalletTests
4 | //
5 | // Created by Jean-Baptiste Dominguez on 2019/02/28.
6 | // Copyright © 2019 Bitcoin.com. All rights reserved.
7 | //
8 |
9 | import Nimble
10 | import Quick
11 | import RxBlocking
12 | @testable import SLPWallet
13 |
14 | class SLPWalletTest: QuickSpec {
15 | override func spec() {
16 |
17 | describe("SLPWallet") {
18 |
19 | beforeEach {
20 | SLPWalletConfig.setRestURL("https://rest.bitcoin.com/v2")
21 | }
22 |
23 | context("Create wallet") {
24 | it("should success") {
25 | let wallet = try! SLPWallet("machine cannon man rail best deliver draw course time tape violin tone", network: .mainnet)
26 |
27 | expect(wallet.mnemonic).to(equal(["machine", "cannon", "man", "rail", "best", "deliver", "draw", "course", "time", "tape", "violin", "tone"]))
28 | expect(wallet.cashAddress).to(equal("bitcoincash:qzd5sk803xqxlmcs6yfwtpwzesq75s5m9c3x6gjl8n"))
29 | expect(wallet.slpAddress).to(equal("simpleledger:qzk92nt0xdxc9qy3yj53h9rjw8dk0s9cqqsrzm5cny"))
30 | expect(wallet.tokens.values.count).to(equal(0))
31 | expect(wallet.getGas()).to(equal(0))
32 | expect(wallet.getPrivKeyByCashAddress("bitcoincash:qzd5sk803xqxlmcs6yfwtpwzesq75s5m9c3x6gjl8n")).toNot(beNil())
33 | expect(wallet.getPrivKeyByCashAddress("bitcoincash:qzk92nt0xdxc9qy3yj53h9rjw8dk0s9cqqucfqpcd6")).toNot(beNil())
34 | expect(wallet.getPrivKeyByCashAddress("test")).to(beNil())
35 |
36 | wallet.scheduler.resume()
37 | wallet.schedulerInterval = 1
38 |
39 | expect(wallet.schedulerInterval).to(equal(1))
40 | }
41 | }
42 |
43 | context("Fetch tokens") {
44 | it("should success") {
45 | let wallet = try! SLPWallet("machine cannon man rail best deliver draw course time tape violin tone", network: .mainnet)
46 | var tokens = try! wallet
47 | .fetchTokens()
48 | .toBlocking()
49 | .single()
50 |
51 | tokens.forEach { tokenId, token in
52 | expect(token.tokenId).toNot(beNil())
53 | expect(token.tokenTicker).toNot(beNil())
54 | expect(token.tokenName).toNot(beNil())
55 | expect(token.decimal).toNot(beNil())
56 | expect(token.getBalance()).toNot(beNil())
57 | expect(token.getGas()).toNot(beNil())
58 | }
59 |
60 | // Fetch a second time to parse utxos
61 | try! wallet
62 | .fetchTokens()
63 | .toBlocking()
64 | .single()
65 | }
66 | }
67 |
68 | context("Add token") {
69 | it("should success") {
70 | let wallet = try! SLPWallet("machine cannon man rail best deliver draw course time tape violin tone", network: .mainnet)
71 | let token = SLPToken("ce7f87ac5d086ad1c736c472ce5bc75f020bf22d3e2ed8603c675a6517b9c1cd")
72 | let newToken = try! wallet
73 | .addToken(token)
74 | .toBlocking()
75 | .single()
76 |
77 | expect(newToken.tokenTicker).to(equal("BCC"))
78 | expect(newToken.tokenName).to(equal("Bitcoin.com Coin"))
79 | expect(newToken.decimal).to(equal(2))
80 | expect(newToken.getBalance()).toNot(beNil())
81 | expect(newToken.getGas()).toNot(beNil())
82 | }
83 | }
84 |
85 | context("Secure storage") {
86 | it("should success") {
87 | let createdWallet = try! SLPWallet(.mainnet, force: true)
88 | let restoredWallet = try! SLPWallet(.mainnet)
89 | expect(restoredWallet.cashAddress).to(equal(createdWallet.cashAddress))
90 | expect(restoredWallet.slpAddress).to(equal(createdWallet.slpAddress))
91 | }
92 | }
93 |
94 | context("Send token") {
95 | it("should success") {
96 | let wallet = try! SLPWallet("machine cannon man rail best deliver draw course time tape violin tone", network: .mainnet)
97 |
98 | let token = SLPToken("e3422fa70647b659272e4124234b7d80855ccdf077b683d3f348b76454090f06")
99 | token._decimal = 2
100 |
101 | let utxo = SLPTokenUTXO("e3422fa70647b659272e4124234b7d80855ccdf077b683d3f348b76454090f06", satoshis: 20000, cashAddress: "bitcoincash:qzk92nt0xdxc9qy3yj53h9rjw8dk0s9cqqucfqpcd6", scriptPubKey: "483045022100e36b594680823bcf7f4a872611cb7652032e92793f2eadae9ad87a57e4854e3602203ffb057332f47bf9f68738f668acad8ae1d2d3265c34e75c9158c9e9be2ae1f0412103b8ac3da9a09a58444291ce21c68a6b279fe33d3e46a879a4c1ed64bd87146506", index: 1, rawTokenQty: 1234)
102 | utxo._isValid = true
103 |
104 | token.addUTXO(utxo)
105 |
106 | wallet._tokens["e3422fa70647b659272e4124234b7d80855ccdf077b683d3f348b76454090f06"] = token
107 |
108 | do {
109 | let value = try SLPTransactionBuilder.build(wallet, tokenId: "e3422fa70647b659272e4124234b7d80855ccdf077b683d3f348b76454090f06", amount: 12, toAddress: "simpleledger:qqs5mxuxr9kaukncpgdc7z64zp6t87rk7cwtkvhpjv")
110 |
111 | wallet.updateUTXOsAfterSending(token, usedUTXOs: value.usedUTXOs, newUTXOs: value.newUTXOs)
112 | expect(wallet.getGas()).to(be(18385))
113 | expect(wallet._tokens["e3422fa70647b659272e4124234b7d80855ccdf077b683d3f348b76454090f06"]?.getBalance()).to(equal(0.34))
114 | } catch {
115 | fail()
116 | }
117 | }
118 | }
119 | }
120 | }
121 | }
122 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | 
2 |
3 | # SLPWallet iOS SDK
4 |
5 | [](https://travis-ci.com/Bitcoin-com/slp-wallet-sdk-ios)
6 | [](https://codecov.io/gh/bitcoin-com/slp-wallet-sdk-ios)
7 | [](https://github.com/Bitcoin-com/CocoaPods/tree/master/SLPWallet/0.1.0)
8 | 
9 | 
10 | 
11 | 
12 |
13 | ## Installation
14 |
15 | ### CocoaPods
16 |
17 | #### Podfile
18 |
19 | ```ruby
20 | # Add our BitcoinKit fork that handles SLP address
21 | source 'https://github.com/Bitcoin-com/CocoaPods.git'
22 | source 'https://github.com/CocoaPods/Specs.git'
23 |
24 | platform :ios, '10.0'
25 |
26 | target 'SLPWalletTestApp' do
27 | use_frameworks!
28 |
29 | # Pods for SLPWalletTestApp
30 | pod 'SLPWallet'
31 |
32 | end
33 | ```
34 | #### Commands
35 |
36 | ```bash
37 | $ brew install autoconf automake // Required with BitcoinKit
38 | $ brew install libtool // Required with BitcoinKit
39 | $ pod install
40 | ```
41 |
42 | #### Pod install issue
43 |
44 | ```bash
45 | sudo xcode-select --switch /Applications/Xcode.app/Contents/Developer/
46 | ```
47 |
48 | ### Configuration
49 |
50 | SLPWallet uses the Keychain to safely store the mnemonic seed phrase on your device. However, you need to create an entitlement file to allow the access to the Keychain. You can have a look at the sample project anytime you need to check the configuration [here.](./Sample/SLPWalletDemo/)
51 |
52 | Under the hood, the SDK is using [KeychainAccess](https://github.com/kishikawakatsumi/KeychainAccess).
53 |
54 | ```xml
55 |
56 |
57 |
58 |
59 | keychain-access-groups
60 |
61 | $(AppIdentifierPrefix)your.bundle.id
62 |
63 |
64 |
65 | ```
66 |
67 | ## Get Started
68 |
69 | ### Setup URL + API Key (Not required :warning:, nice to have :dash:)
70 |
71 | The SDK uses https://rest.bitcoin.com which by default is limited to up to 60 calls per minute per IP address. If you would like to increase your REST calls limit rate, please contact [Bitcoin.com's team](https://developer.bitcoin.com/rest/) to obtain an API key. You may configure the SDK to work with an API key or with your own REST API, as shown below:
72 |
73 | Add your setup to your ```AppDelegate.swift``` as follows:
74 |
75 | 1. Add the following import statement:
76 |
77 | ```Swift
78 | Import SLPWallet
79 | ```
80 |
81 | 2. Setup in the ```application(_:didFinishLaunchingWithOptions:)```
82 |
83 | ```Swift
84 | // Optional setup
85 | SLPWalletConfig.setRestAPIKey("MY_API_KEY") // Optional
86 | SLPWalletConfig.setRestURL("https://rest.bitcoin.com") // By default => https://rest.bitcoin.com
87 | ```
88 |
89 | ### Creating new wallet with/without mnemonic
90 |
91 | The wallet works with only 2 addresses, using:
92 | - the SLP recommended path 44'/245'/0' + m/0/0 (handling tokens - bch with token + token change address)
93 | - the BCH recommended path 44'/145'/0' + m/0/0 (handling gas - bch without token + bch change address)
94 |
95 | However, both paths are scanned to get any bch or tokens available.
96 |
97 | ```swift
98 | // Init 1
99 | // Generate/Restore a wallet + Save/Get in Keychain
100 | // If mnemonic in Keychain
101 | // Restore wallet
102 | // else
103 | // Generate mnemonic
104 | let wallet = try SLPWallet(.testnet) // .mainnet or .testnet
105 |
106 | // Init 2
107 | // Restore a wallet from Mnemonic + Save in Keychain
108 | let wallet = try SLPWallet("My Mnemonic", network: .testnet) // .mainnet or .testnet
109 |
110 | // Init 3
111 | // Generate a wallet
112 | // If force == true
113 | // Generate everytime a new wallet
114 | // else
115 | // => Init 1
116 | let wallet = try SLPWallet(.testnet, force: Bool) // .mainnet or .testnet
117 | ```
118 |
119 | ### Addresses + tokens
120 |
121 | ```swift
122 | wallet.mnemonic // [String]
123 | wallet.slpAddress // String
124 | wallet.cashAddress // String
125 | wallet.tokens // [String:SLPToken] Tokens are accessible after an initial fetch or if you have started the scheduler
126 | ```
127 | ### Fetch my tokens
128 |
129 | ```swift
130 | wallet
131 | .fetchTokens() // RxSwift => Single<[String:Token]>
132 | .subscribe(onSuccess: { tokens in
133 | // My tokens
134 | tokens.forEach { tokenId, token in
135 | token.tokenId
136 | token.tokenName
137 | token.tokenTicker
138 | token.decimal
139 | token.getBalance()
140 | token.getGas()
141 | }
142 | }, onError: { error in
143 | // ...
144 | })
145 | ```
146 | ### Send token
147 |
148 | ```swift
149 | wallet
150 | .sendToken(tokenId, amount: amount, toAddress: toAddress) // toAddress can be a slp / cash address or legacy
151 | .subscribe(onSuccess: { txid in // RxSwift => Single
152 | // ...
153 | }, onError: { error in
154 | // ...
155 | })
156 | ```
157 | ### Auto update wallet/tokens (balances + gas)
158 |
159 | ```swift
160 | // Start & Stop
161 | wallet.scheduler.resume()
162 | wallet.scheduler.suspend()
163 |
164 | // Change the interval
165 | wallet.schedulerInterval = 10 // in seconds (30 by default)
166 | ```
167 |
168 | ### WalletDelegate called when :
169 | + scheduler is started + token balance changed
170 |
171 | ```swift
172 | class MyViewController: SLPWalletDelegate {
173 |
174 |
175 | override func viewDidLoad() {
176 | super.viewDidLoad()
177 |
178 | let wallet = ... // Setup a wallet
179 | wallet.delegate = self
180 | }
181 |
182 | func onUpdatedToken(_ token: SLPToken) {
183 | // My updated token
184 | token.tokenId
185 | token.tokenName
186 | token.tokenTicker
187 | token.decimal
188 | token.getBalance()
189 | token.getGas()
190 | }
191 | }
192 | ```
193 |
194 | ## Sample Project
195 |
196 | [iOS project developed with SLPWallet SDK](https://github.com/Bitcoin-com/slp-wallet-sdk-ios-demo)
197 |
198 | 
199 |
200 | ## Authors & Maintainers
201 | - Jean-Baptiste Dominguez [[Github](https://github.com/jbdtky), [Twitter](https://twitter.com/jbdtky)]
202 |
203 | ## References
204 | - [BIP44](https://github.com/bitcoin/bips/blob/master/bip-0044.mediawiki)
205 | - [Simple Ledger Protocol (SLP)](https://github.com/simpleledger/slp-specifications/blob/master/slp-token-type-1.md)
206 |
207 | ## Credits
208 | - [KeychainAccess](https://github.com/kishikawakatsumi/KeychainAccess)
209 | - [RxSwift](https://github.com/ReactiveX/RxSwift)
210 | - [Moya](https://github.com/Moya/Moya)
211 | - [BitcoinKit](https://github.com/Bitcoin-com/BitcoinKit)
212 | - [Kishikawa Katsumi](https://github.com/kishikawakatsumi) for BitcoinKit + KeychainAccess
213 |
214 | ## License
215 |
216 | SLPWallet iOS SDK is available under the MIT license. See the LICENSE file for more info.
217 |
--------------------------------------------------------------------------------
/SLPWallet/Utils/SLPTransactionParser.swift:
--------------------------------------------------------------------------------
1 | //
2 | // SLPTransactionParser.swift
3 | // SLPWallet
4 | //
5 | // Created by Jean-Baptiste Dominguez on 2019/03/01.
6 | // Copyright © 2019 Bitcoin.com. All rights reserved.
7 | //
8 |
9 | // TODO: Move all the parsering here to clean SLPWallet
10 | //
11 |
12 | import BitcoinKit
13 |
14 | enum SLPTransactionType: String {
15 | case GENESIS
16 | case SEND
17 | case MINT
18 | }
19 |
20 | struct SLPTransactionParserResponse {
21 | var token: SLPToken
22 | var utxos: [SLPWalletUTXO]
23 | }
24 |
25 | class SLPTransactionParser {
26 |
27 | static func parse(_ tx: RestService.ResponseTx, vouts: [Int]) -> SLPTransactionParserResponse? {
28 |
29 | let parsedToken = SLPToken()
30 | var parsedUTXOs = [SLPWalletUTXO]()
31 |
32 | // TODO: Parse the tx in another place
33 | let script = Script(hex: tx.vout[0].scriptPubKey.hex)
34 |
35 | var voutToTokenQty = [Int]()
36 | voutToTokenQty.append(0) // To have the same mapping with the vouts
37 |
38 | var mintVout = 0
39 |
40 | if var chunks = script?.scriptChunks
41 | , chunks.removeFirst().opCode == .OP_RETURN {
42 |
43 | // 0 : lokad id 4 bytes ASCII
44 | // Good
45 | guard let lokadId = chunks[0].chunkData.removeLeft().removeRight().stringASCII else {
46 | return nil
47 | }
48 |
49 | if lokadId == "SLP" {
50 |
51 | // 1 : token_type 1 bytes Integer
52 | // Good
53 | var chunk = chunks[1].chunkData.removeLeft()
54 | let tokenType = chunk.uint8 // Unused for now
55 |
56 | // 2 : transaction_type 4 bytes ASCII
57 | // Good
58 | chunk = chunks[2].chunkData.removeLeft()
59 |
60 | guard let transactionType = chunks[2].chunkData.removeLeft().stringASCII else {
61 | return nil
62 | }
63 |
64 | switch transactionType {
65 | case SLPTransactionType.GENESIS.rawValue:
66 |
67 | // Genesis => Txid
68 | let tokenId = tx.txid
69 | parsedToken._tokenId = tokenId
70 |
71 | // 3 : token_ticker UTF8
72 | // Good
73 | chunk = chunks[3].chunkData.removeLeft()
74 | guard let tokenTicker = chunk.stringUTF8 else {
75 | return nil
76 | }
77 | parsedToken._tokenTicker = tokenTicker
78 |
79 | // 4 : token_name UTF8
80 | // Good
81 | chunk = chunks[4].chunkData.removeLeft()
82 | guard let tokenName = chunk.stringUTF8 else {
83 | return nil
84 | }
85 | parsedToken._tokenName = tokenName
86 |
87 | // 7 : decimal 1 Byte
88 | // Good
89 | chunk = chunks[7].chunkData.removeLeft()
90 | guard let decimal = Int(chunk.hex, radix: 16) else {
91 | return nil
92 | }
93 | parsedToken._decimal = decimal
94 |
95 | // 8 : Mint 2 Bytes
96 | // Good
97 | chunk = chunks[8].chunkData.removeLeft()
98 | if let mv = Int(chunk.hex, radix: 16) {
99 | mintVout = mv
100 | }
101 |
102 | // 9 to .. : initial_token_mint_quantity 8 Bytes
103 | // Good
104 | chunk = chunks[9].chunkData.removeLeft()
105 | if let balance = Int(chunk.hex, radix: 16) {
106 | voutToTokenQty.append(balance)
107 | }
108 |
109 | case SLPTransactionType.SEND.rawValue:
110 |
111 | // 3 : token_id 32 bytes hex
112 | // Good
113 | chunk = chunks[3].chunkData.removeLeft()
114 | let tokenId = chunk.hex
115 | parsedToken._tokenId = tokenId
116 |
117 | // 4 to .. : token_output_quantity 1..19 8 Bytes / qty
118 | for i in 4...chunks.count - 1 {
119 | chunk = chunks[i].chunkData.removeLeft()
120 | if let balance = Int(chunk.hex, radix: 16) {
121 | voutToTokenQty.append(balance)
122 | } else {
123 | break
124 | }
125 | }
126 | case SLPTransactionType.MINT.rawValue:
127 |
128 | // 3 : token_id 32 bytes hex
129 | // Good
130 | chunk = chunks[3].chunkData.removeLeft()
131 | let tokenId = chunk.hex
132 | parsedToken._tokenId = tokenId
133 |
134 | // 4 : Mint 2 Bytes
135 | // Good
136 | chunk = chunks[4].chunkData.removeLeft()
137 | if let mv = Int(chunk.hex, radix: 16) {
138 | mintVout = mv
139 | }
140 |
141 | // 5 : additional_token_quantity 8 Bytes
142 | // Good
143 | chunk = chunks[5].chunkData.removeLeft()
144 | if let balance = Int(chunk.hex, radix: 16) {
145 | voutToTokenQty.append(balance)
146 | }
147 | default: break
148 | }
149 | }
150 | }
151 |
152 | // Get the vouts that we are interested in
153 | vouts.forEach { i in
154 | let vout = tx.vout[i]
155 |
156 | guard let rawAddress = vout.scriptPubKey.addresses?.first
157 | , let address = try? AddressFactory.create(rawAddress) else {
158 | return
159 | }
160 |
161 | let cashAddress = address.cashaddr
162 |
163 | guard vout.n < voutToTokenQty.count
164 | , voutToTokenQty.count > 1
165 | , voutToTokenQty[vout.n] > 0 else { // Because we push 1 vout qty by default for the OP_RETURN
166 |
167 | // We need to avoid using the mint baton
168 | if vout.n == mintVout && mintVout > 0 {
169 | // UTXO with baton
170 | parsedToken._mintUTXO = SLPWalletUTXO(tx.txid, satoshis: vout.value.toSatoshis(), cashAddress: cashAddress, scriptPubKey: vout.scriptPubKey.hex, index: vout.n)
171 | } else {
172 | // UTXO without token
173 | let utxo = SLPWalletUTXO(tx.txid, satoshis: vout.value.toSatoshis(), cashAddress: cashAddress, scriptPubKey: vout.scriptPubKey.hex, index: vout.n)
174 | parsedUTXOs.append(utxo)
175 | }
176 |
177 | return
178 | }
179 |
180 | // UTXO with a token
181 | let rawTokenQty = voutToTokenQty[vout.n]
182 | let tokenUTXO = SLPTokenUTXO(tx.txid, satoshis: vout.value.toSatoshis(), cashAddress: cashAddress, scriptPubKey: vout.scriptPubKey.hex, index: vout.n, rawTokenQty: rawTokenQty)
183 | parsedToken.addUTXO(tokenUTXO)
184 | }
185 |
186 | return SLPTransactionParserResponse(token: parsedToken, utxos: parsedUTXOs)
187 | }
188 | }
189 |
--------------------------------------------------------------------------------
/SLPWalletTests/SLPTransactionParserTest.swift:
--------------------------------------------------------------------------------
1 | //
2 | // SLPTransactionParserTest.swift
3 | // SLPWalletTests
4 | //
5 | // Created by Jean-Baptiste Dominguez on 2019/03/25.
6 | // Copyright © 2019 Bitcoin.com. All rights reserved.
7 | //
8 |
9 | import Nimble
10 | import Quick
11 | @testable import SLPWallet
12 |
13 | class SLPTransactionParserTest: QuickSpec {
14 | override func spec() {
15 | describe("SLPTransactionParser") {
16 | context("Parse a transaction GENESIS") {
17 |
18 | it("should success") {
19 | let path = Bundle(for: type(of: self)).path(forResource: "tx_details_genesis_tst", ofType: "json")
20 | let url = URL(fileURLWithPath: path!)
21 |
22 | let data = try! Data(contentsOf: url)
23 | let tx = try! JSONDecoder().decode(RestService.ResponseTx.self, from: data)
24 |
25 | guard let parsedData = SLPTransactionParser.parse(tx, vouts: [1, 2]) else {
26 | fail()
27 | return
28 | }
29 |
30 | // Token
31 | expect(parsedData.token.tokenId).to(equal("9cc1cf24e502554d2d3d09918c27decda2c260762961acd469c5473fbcfe192e"))
32 | expect(parsedData.token.tokenName).to(equal("Try! Swift Token"))
33 | expect(parsedData.token.tokenTicker).to(equal("TST"))
34 | expect(parsedData.token.decimal).to(equal(2))
35 |
36 | // Token UTXOs
37 | expect(parsedData.token._utxos.first).toNot(beNil())
38 |
39 | guard let utxo = parsedData.token._utxos.first else {
40 | fail()
41 | return
42 | }
43 |
44 | expect(utxo.txid).to(equal("9cc1cf24e502554d2d3d09918c27decda2c260762961acd469c5473fbcfe192e"))
45 | expect(utxo.rawTokenQty).to(equal(100000000000))
46 | expect(utxo.satoshis).to(equal(546))
47 | expect(utxo.cashAddress).to(equal("bitcoincash:qp4zxnnce2wy8ackzc29wtjp2xw9e6sgyvuv77vvmh"))
48 | expect(utxo.index).to(equal(1))
49 |
50 | // Baton
51 | expect(parsedData.token.mintUTXO).toNot(beNil())
52 | expect(parsedData.token.mintUTXO?.txid).to(equal("9cc1cf24e502554d2d3d09918c27decda2c260762961acd469c5473fbcfe192e"))
53 | expect(parsedData.token.mintUTXO?.cashAddress).to(equal("bitcoincash:qp4zxnnce2wy8ackzc29wtjp2xw9e6sgyvuv77vvmh"))
54 | expect(parsedData.token.mintUTXO?.satoshis).to(equal(546))
55 | expect(parsedData.token.mintUTXO?.index).to(equal(2))
56 |
57 | // UTXOs
58 | expect(parsedData.utxos.count).to(equal(0))
59 | }
60 | }
61 | }
62 |
63 | context("Parse a transaction SEND") {
64 |
65 | it("should success") {
66 | let path = Bundle(for: type(of: self)).path(forResource: "tx_details_send_tst", ofType: "json")
67 | let url = URL(fileURLWithPath: path!)
68 |
69 | let data = try! Data(contentsOf: url)
70 | let tx = try! JSONDecoder().decode(RestService.ResponseTx.self, from: data)
71 |
72 | guard let parsedData = SLPTransactionParser.parse(tx, vouts: [2, 3]) else {
73 | fail()
74 | return
75 | }
76 |
77 | // Token
78 | expect(parsedData.token.tokenId).to(equal("9cc1cf24e502554d2d3d09918c27decda2c260762961acd469c5473fbcfe192e"))
79 | expect(parsedData.token.tokenName).to(beNil())
80 | expect(parsedData.token.tokenTicker).to(beNil())
81 | expect(parsedData.token.decimal).to(beNil())
82 |
83 | // Token UTXOs
84 | expect(parsedData.token._utxos.first).toNot(beNil())
85 |
86 | guard let tokenUTXO = parsedData.token._utxos.first else {
87 | fail()
88 | return
89 | }
90 |
91 | expect(tokenUTXO.txid).to(equal("a9f639148662ca6376c3650f3d7e6dffbe9a477cf947499bfcb2c85412331c2e"))
92 | expect(tokenUTXO.rawTokenQty).to(equal(15080066))
93 | expect(tokenUTXO.satoshis).to(equal(546))
94 | expect(tokenUTXO.cashAddress).to(equal("bitcoincash:qzdwxdtprwxhynpvcqngtupp3tn58smte5z2yfqe0v"))
95 | expect(tokenUTXO.index).to(equal(2))
96 |
97 | // Baton
98 | expect(parsedData.token.mintUTXO).to(beNil())
99 |
100 | // UTXOs
101 | expect(parsedData.utxos.first).toNot(beNil())
102 |
103 | guard let utxo = parsedData.utxos.first else {
104 | fail()
105 | return
106 | }
107 |
108 | expect(utxo.txid).to(equal("a9f639148662ca6376c3650f3d7e6dffbe9a477cf947499bfcb2c85412331c2e"))
109 | expect(utxo.satoshis).to(equal(192966))
110 | expect(utxo.cashAddress).to(equal("bitcoincash:qqga5ljshfug5g27532d9xc0w55yxdjzwygw62c0nk"))
111 | expect(utxo.index).to(equal(3))
112 | }
113 | }
114 |
115 | context("Parse a transaction MINT") {
116 |
117 | it("should success") {
118 | let path = Bundle(for: type(of: self)).path(forResource: "tx_details_mint_lvl001", ofType: "json")
119 | let url = URL(fileURLWithPath: path!)
120 |
121 | let data = try! Data(contentsOf: url)
122 | let tx = try! JSONDecoder().decode(RestService.ResponseTx.self, from: data)
123 |
124 | guard let parsedData = SLPTransactionParser.parse(tx, vouts: [1, 2]) else {
125 | fail()
126 | return
127 | }
128 |
129 | // Token
130 | expect(parsedData.token.tokenId).to(equal("d5efb237f43a822ede2086bbefca44f1157b7adf2ddeed87c4b294bd136d1d36"))
131 | expect(parsedData.token.tokenName).to(beNil())
132 | expect(parsedData.token.tokenTicker).to(beNil())
133 | expect(parsedData.token.decimal).to(beNil())
134 |
135 | // Token UTXOs
136 | expect(parsedData.token._utxos.first).toNot(beNil())
137 |
138 | guard let tokenUTXO = parsedData.token._utxos.first else {
139 | fail()
140 | return
141 | }
142 |
143 | expect(tokenUTXO.txid).to(equal("c3b72361cee1a7ed5d0911714da7439313eaf22fde842f871656e4d438eba7d1"))
144 | expect(tokenUTXO.rawTokenQty).to(equal(1))
145 | expect(tokenUTXO.satoshis).to(equal(546))
146 | expect(tokenUTXO.cashAddress).to(equal("bitcoincash:qzsn8qeupph6pf8kyn2x79afff7pygzfvqnjwvhmzm"))
147 | expect(tokenUTXO.index).to(equal(1))
148 |
149 | // Baton
150 | expect(parsedData.token.mintUTXO).toNot(beNil())
151 | expect(parsedData.token.mintUTXO?.txid).to(equal("c3b72361cee1a7ed5d0911714da7439313eaf22fde842f871656e4d438eba7d1"))
152 | expect(parsedData.token.mintUTXO?.cashAddress).to(equal("bitcoincash:qrgwrx7hvd27jqz8q5fmgr4kg5cy76yp05a3prllmr"))
153 | expect(parsedData.token.mintUTXO?.satoshis).to(equal(546))
154 | expect(parsedData.token.mintUTXO?.index).to(equal(2))
155 |
156 | // UTXOs
157 | expect(parsedData.utxos.first).to(beNil())
158 | }
159 | }
160 | }
161 | }
162 |
--------------------------------------------------------------------------------
/SLPWalletTests/SLPTransactionBuilderTest.swift:
--------------------------------------------------------------------------------
1 | //
2 | // SLPTransactionBuilderTest.swift
3 | // SLPWalletTests
4 | //
5 | // Created by Jean-Baptiste Dominguez on 2019/03/12.
6 | // Copyright © 2019 Bitcoin.com. All rights reserved.
7 | //
8 |
9 | import Nimble
10 | import Quick
11 | @testable import SLPWallet
12 |
13 | class SLPTransactionBuilderTest: QuickSpec {
14 | override func spec() {
15 | describe("SLPTransactionBuilder") {
16 | context("Build a transaction") {
17 |
18 | it("should fail TOKEN_NOT_FOUND") {
19 | let wallet = try! SLPWallet("machine cannon man rail best deliver draw course time tape violin tone", network: .mainnet)
20 |
21 | do {
22 | _ = try SLPTransactionBuilder.build(wallet, tokenId: "test", amount: 12, toAddress: "simpleledger:qzk92nt0xdxc9qy3yj53h9rjw8dk0s9cqqsrzm5cny")
23 | fail()
24 | } catch SLPTransactionBuilderError.TOKEN_NOT_FOUND {
25 | // Success
26 | } catch {
27 | fail()
28 | }
29 | }
30 |
31 | it("should fail DECIMAL_NOT_AVAILABLE") {
32 | let wallet = try! SLPWallet("machine cannon man rail best deliver draw course time tape violin tone", network: .mainnet)
33 |
34 | let token = SLPToken("e3422fa70647b659272e4124234b7d80855ccdf077b683d3f348b76454090f06")
35 |
36 | let utxo = SLPTokenUTXO("e3422fa70647b659272e4124234b7d80855ccdf077b683d3f348b76454090f06", satoshis: 1000, cashAddress: "bitcoincash:qzk92nt0xdxc9qy3yj53h9rjw8dk0s9cqqucfqpcd6", scriptPubKey: "483045022100e36b594680823bcf7f4a872611cb7652032e92793f2eadae9ad87a57e4854e3602203ffb057332f47bf9f68738f668acad8ae1d2d3265c34e75c9158c9e9be2ae1f0412103b8ac3da9a09a58444291ce21c68a6b279fe33d3e46a879a4c1ed64bd87146506", index: 1, rawTokenQty: 1234)
37 | token.addUTXO(utxo)
38 |
39 | wallet._tokens["e3422fa70647b659272e4124234b7d80855ccdf077b683d3f348b76454090f06"] = token
40 |
41 | do {
42 | _ = try SLPTransactionBuilder.build(wallet, tokenId: "e3422fa70647b659272e4124234b7d80855ccdf077b683d3f348b76454090f06", amount: 12, toAddress: "simpleledger:qzk92nt0xdxc9qy3yj53h9rjw8dk0s9cqqsrzm5cny")
43 | fail()
44 | } catch SLPTransactionBuilderError.DECIMAL_NOT_AVAILABLE {
45 | // Success
46 | } catch {
47 | fail()
48 | }
49 | }
50 |
51 | it("should fail INSUFFICIENT_FUNDS") {
52 | let wallet = try! SLPWallet("machine cannon man rail best deliver draw course time tape violin tone", network: .mainnet)
53 |
54 | let token = SLPToken("e3422fa70647b659272e4124234b7d80855ccdf077b683d3f348b76454090f06")
55 | token._decimal = 0
56 |
57 | let utxo = SLPTokenUTXO("e3422fa70647b659272e4124234b7d80855ccdf077b683d3f348b76454090f06", satoshis: 1000, cashAddress: "bitcoincash:qzk92nt0xdxc9qy3yj53h9rjw8dk0s9cqqucfqpcd6", scriptPubKey: "483045022100e36b594680823bcf7f4a872611cb7652032e92793f2eadae9ad87a57e4854e3602203ffb057332f47bf9f68738f668acad8ae1d2d3265c34e75c9158c9e9be2ae1f0412103b8ac3da9a09a58444291ce21c68a6b279fe33d3e46a879a4c1ed64bd87146506", index: 1, rawTokenQty: 1234)
58 | utxo._isValid = true
59 |
60 | token.addUTXO(utxo)
61 |
62 | wallet._tokens["e3422fa70647b659272e4124234b7d80855ccdf077b683d3f348b76454090f06"] = token
63 |
64 | do {
65 | _ = try SLPTransactionBuilder.build(wallet, tokenId: "e3422fa70647b659272e4124234b7d80855ccdf077b683d3f348b76454090f06", amount: 1235, toAddress: "simpleledger:qzk92nt0xdxc9qy3yj53h9rjw8dk0s9cqqsrzm5cny")
66 | fail()
67 | } catch SLPTransactionBuilderError.INSUFFICIENT_FUNDS {
68 | // Success
69 | } catch {
70 | fail()
71 | }
72 | }
73 |
74 | it("should fail CONVERSION_METADATA") {
75 | let wallet = try! SLPWallet("machine cannon man rail best deliver draw course time tape violin tone", network: .mainnet)
76 |
77 | let token = SLPToken("test")
78 | token._decimal = 0
79 |
80 | let utxo = SLPTokenUTXO("e3422fa70647b659272e4124234b7d80855ccdf077b683d3f348b76454090f06", satoshis: 1000, cashAddress: "bitcoincash:qzk92nt0xdxc9qy3yj53h9rjw8dk0s9cqqucfqpcd6", scriptPubKey: "483045022100e36b594680823bcf7f4a872611cb7652032e92793f2eadae9ad87a57e4854e3602203ffb057332f47bf9f68738f668acad8ae1d2d3265c34e75c9158c9e9be2ae1f0412103b8ac3da9a09a58444291ce21c68a6b279fe33d3e46a879a4c1ed64bd87146506", index: 1, rawTokenQty: 1234)
81 | utxo._isValid = true
82 |
83 | token.addUTXO(utxo)
84 |
85 | wallet._tokens["test"] = token
86 |
87 | do {
88 | _ = try SLPTransactionBuilder.build(wallet, tokenId: "test", amount: 1234, toAddress: "simpleledger:qzk92nt0xdxc9qy3yj53h9rjw8dk0s9cqqsrzm5cny")
89 | fail()
90 | } catch SLPTransactionBuilderError.CONVERSION_METADATA {
91 | // Success
92 | } catch {
93 | fail()
94 | }
95 | }
96 |
97 | it("should fail GAS_INSUFFISANT") {
98 | let wallet = try! SLPWallet("machine cannon man rail best deliver draw course time tape violin tone", network: .mainnet)
99 |
100 | let token = SLPToken("e3422fa70647b659272e4124234b7d80855ccdf077b683d3f348b76454090f06")
101 | token._decimal = 2
102 |
103 | let utxo = SLPTokenUTXO("e3422fa70647b659272e4124234b7d80855ccdf077b683d3f348b76454090f06", satoshis: 546, cashAddress: "bitcoincash:qzk92nt0xdxc9qy3yj53h9rjw8dk0s9cqqucfqpcd6", scriptPubKey: "483045022100e36b594680823bcf7f4a872611cb7652032e92793f2eadae9ad87a57e4854e3602203ffb057332f47bf9f68738f668acad8ae1d2d3265c34e75c9158c9e9be2ae1f0412103b8ac3da9a09a58444291ce21c68a6b279fe33d3e46a879a4c1ed64bd87146506", index: 1, rawTokenQty: 1234)
104 | utxo._isValid = true
105 |
106 | token.addUTXO(utxo)
107 |
108 | wallet._tokens["e3422fa70647b659272e4124234b7d80855ccdf077b683d3f348b76454090f06"] = token
109 |
110 | do {
111 | _ = try SLPTransactionBuilder.build(wallet, tokenId: "e3422fa70647b659272e4124234b7d80855ccdf077b683d3f348b76454090f06", amount: 11, toAddress: "simpleledger:qzk92nt0xdxc9qy3yj53h9rjw8dk0s9cqqsrzm5cny")
112 | fail()
113 | } catch SLPTransactionBuilderError.GAS_INSUFFICIENT {
114 | // Success
115 | } catch ( _){
116 | fail()
117 | }
118 | }
119 |
120 | it("should success") {
121 | let wallet = try! SLPWallet("machine cannon man rail best deliver draw course time tape violin tone", network: .mainnet)
122 |
123 | let token = SLPToken("e3422fa70647b659272e4124234b7d80855ccdf077b683d3f348b76454090f06")
124 | token._decimal = 2
125 |
126 | let utxo = SLPTokenUTXO("e3422fa70647b659272e4124234b7d80855ccdf077b683d3f348b76454090f06", satoshis: 20000, cashAddress: "bitcoincash:qzk92nt0xdxc9qy3yj53h9rjw8dk0s9cqqucfqpcd6", scriptPubKey: "483045022100e36b594680823bcf7f4a872611cb7652032e92793f2eadae9ad87a57e4854e3602203ffb057332f47bf9f68738f668acad8ae1d2d3265c34e75c9158c9e9be2ae1f0412103b8ac3da9a09a58444291ce21c68a6b279fe33d3e46a879a4c1ed64bd87146506", index: 1, rawTokenQty: 1234)
127 | utxo._isValid = true
128 |
129 | token.addUTXO(utxo)
130 |
131 | wallet._tokens["e3422fa70647b659272e4124234b7d80855ccdf077b683d3f348b76454090f06"] = token
132 |
133 | do {
134 | let value = try SLPTransactionBuilder.build(wallet, tokenId: "e3422fa70647b659272e4124234b7d80855ccdf077b683d3f348b76454090f06", amount: 12, toAddress: "simpleledger:qqs5mxuxr9kaukncpgdc7z64zp6t87rk7cwtkvhpjv")
135 |
136 | expect(value.usedUTXOs.count).to(equal(1))
137 | expect(value.newUTXOs.count).to(equal(2))
138 | expect(value.rawTx).to(equal("0100000001060f095464b748f3d383b677f0cd5c85807d4b2324412e2759b64706a72f42e3010000006a4730440220271ddd30e1b0326fcb47c983e4138b9532f457dde525eded2b6edb63d986504d02200c6d8cbf7e0d3b8b089312f6b9aa3e3dff6baa556611ea86fd0557e770eae57e41210329d5ffda1250d97614cfd3a5cb1c89d0a255c59584c091915b21b3e64137fe7affffffff040000000000000000406a04534c500001010453454e4420e3422fa70647b659272e4124234b7d80855ccdf077b683d3f348b76454090f060800000000000004b008000000000000002222020000000000001976a914214d9b86196dde5a780a1b8f0b551074b3f876f688ac22020000000000001976a914ac554d6f334d82809124a91b947271db67c0b80088acd1470000000000001976a9149b4858ef89806fef10d112e585c2cc01ea429b2e88ac00000000"))
139 | } catch {
140 | fail()
141 | }
142 | }
143 | }
144 | }
145 | }
146 | }
147 |
148 |
--------------------------------------------------------------------------------
/SLPWallet/Utils/SLPTransactionBuilder.swift:
--------------------------------------------------------------------------------
1 | //
2 | // SLPTransactionBuilder.swift
3 | // SLPWallet
4 | //
5 | // Created by Jean-Baptiste Dominguez on 2019/03/04.
6 | // Copyright © 2019 Bitcoin.com. All rights reserved.
7 | //
8 |
9 | import Foundation
10 | import BitcoinKit
11 |
12 | public enum SLPTransactionBuilderError: String, Error {
13 | case CONVERSION_METADATA
14 | case CONVERSION_AMOUNT
15 | case CONVERSION_CHANGE
16 | case DECIMAL_NOT_AVAILABLE
17 | case GAS_INSUFFICIENT
18 | case INSUFFICIENT_FUNDS
19 | case SCRIPT_TO
20 | case SCRIPT_TOKEN_CHANGE
21 | case SCRIPT_CHANGE
22 | case TO_ADDRESS_INVALID
23 | case TOKEN_NOT_FOUND
24 | case WALLET_ADDRESS_INVALID
25 | }
26 |
27 | struct SLPTransactionBuilderResponse {
28 | var rawTx: String
29 | var usedUTXOs: [SLPWalletUTXO]
30 | var newUTXOs: [SLPWalletUTXO]
31 | }
32 |
33 | class SLPTransactionBuilder {
34 |
35 | static func build(_ wallet: SLPWallet, tokenId: String, amount: Double, toAddress: String) throws -> SLPTransactionBuilderResponse {
36 |
37 | let minSatoshisForToken = UInt64(546)
38 | var satoshisForTokens: UInt64 = minSatoshisForToken
39 | let satoshisForInput = 148 + 200
40 | var tokenInputs = 1
41 | var privKeys = [PrivateKey]()
42 | var newUTXOs = [SLPWalletUTXO]()
43 | var usedUTXOs = [SLPWalletUTXO]()
44 |
45 | guard let token = wallet.tokens[tokenId] else {
46 | // Token doesn't exist
47 | throw SLPTransactionBuilderError.TOKEN_NOT_FOUND
48 | }
49 |
50 | guard let decimal = token.decimal else {
51 | throw SLPTransactionBuilderError.DECIMAL_NOT_AVAILABLE
52 | }
53 |
54 | guard token.getBalance() >= amount else {
55 | // Insufficent balance
56 | throw SLPTransactionBuilderError.INSUFFICIENT_FUNDS
57 | }
58 |
59 | // change amount
60 | let rawTokenAmount = TokenQtyConverter.convertToRawQty(amount, decimal: decimal)
61 |
62 | guard let tokenId = token.tokenId
63 | , let tokenIdInData = Data(hex: tokenId)
64 | , let lokadIdInData = Data(hex: "534c5000")
65 | , let tokenTypeInData = Data(hex: "01")
66 | , let actionInData = "SEND".data(using: String.Encoding.ascii) else {
67 | throw SLPTransactionBuilderError.CONVERSION_METADATA
68 | }
69 |
70 | guard let amountInData = TokenQtyConverter.convertToData(rawTokenAmount) else {
71 | throw SLPTransactionBuilderError.CONVERSION_AMOUNT
72 | }
73 |
74 | let newScript = try Script()
75 | .append(.OP_RETURN)
76 | .appendData(lokadIdInData)
77 | .appendData(tokenTypeInData)
78 | .appendData(actionInData)
79 | .appendData(tokenIdInData)
80 | .appendData(amountInData)
81 |
82 | // I can start to create my transaction here :)
83 |
84 | // UTXOs selection from SLPTokenUTXOs
85 | var sum = 0
86 | var selectedTokenUTXOs: [SLPTokenUTXO] = token.utxos
87 | .filter { utxo -> Bool in
88 | guard sum < rawTokenAmount
89 | , let privKey = wallet.getPrivKeyByCashAddress(utxo.cashAddress) else {
90 | return false
91 | }
92 |
93 | privKeys.append(privKey)
94 | sum += utxo.rawTokenQty
95 | return true
96 | }
97 | .compactMap { $0 }
98 |
99 | let rawTokenChange = sum - rawTokenAmount
100 |
101 | // Case we don't have the PrivKey of utxos and didn't get enough tokens
102 | if rawTokenChange < 0 {
103 | throw SLPTransactionBuilderError.INSUFFICIENT_FUNDS
104 | }
105 |
106 | if rawTokenChange > 0 {
107 | guard let changeInData = TokenQtyConverter.convertToData(rawTokenChange) else {
108 | // throw an exception
109 | throw SLPTransactionBuilderError.CONVERSION_CHANGE
110 | }
111 |
112 | try newScript.appendData(changeInData)
113 | satoshisForTokens += minSatoshisForToken
114 | tokenInputs += 1
115 | }
116 |
117 | usedUTXOs.append(contentsOf: selectedTokenUTXOs)
118 | var selectedUTXOs = selectedTokenUTXOs.map { utxo -> UnspentTransaction in
119 | return utxo.asUnspentTransaction()
120 | }
121 |
122 | guard let tokenChangeAddress = try? AddressFactory.create(wallet.SLPAccount.cashAddress) else {
123 | throw SLPTransactionBuilderError.WALLET_ADDRESS_INVALID
124 | }
125 |
126 | guard let lockScriptTokenChange = Script(address: tokenChangeAddress) else {
127 | // throw exception
128 | throw SLPTransactionBuilderError.SCRIPT_TOKEN_CHANGE
129 | }
130 |
131 | guard let cashChangeAddress = try? AddressFactory.create(wallet.BCHAccount.cashAddress) else {
132 | throw SLPTransactionBuilderError.WALLET_ADDRESS_INVALID
133 | }
134 |
135 | guard let lockScriptCashChange = Script(address: cashChangeAddress) else {
136 | // throw exception
137 | throw SLPTransactionBuilderError.SCRIPT_CHANGE
138 | }
139 |
140 | guard let toAddress = try? AddressFactory.create(toAddress) else {
141 | throw SLPTransactionBuilderError.TO_ADDRESS_INVALID
142 | }
143 |
144 | guard let lockScriptTo = Script(address: toAddress) else {
145 | // throw exception
146 | throw SLPTransactionBuilderError.SCRIPT_TO
147 | }
148 |
149 |
150 | let opOutput = TransactionOutput(value: 0, lockingScript: newScript.data)
151 | let toOutput = TransactionOutput(value: minSatoshisForToken, lockingScript: lockScriptTo.data)
152 |
153 | var outputs: [TransactionOutput] = [opOutput, toOutput]
154 |
155 | if rawTokenChange > 0 {
156 | let tokenChangeOutput = TransactionOutput(value: minSatoshisForToken, lockingScript: lockScriptTokenChange.data)
157 | outputs.append(tokenChangeOutput)
158 | }
159 |
160 | let totalAmount: UInt64 = selectedUTXOs.reduce(0) { $0 + $1.output.value }
161 |
162 | // 9 = 8 + 1 unsigned Int quantity of tokens
163 | // 9 = value of OP_RETURN (same as previously)
164 | // 46 = value of OP_RETURN data
165 | // 34 = value of output
166 | // 148 = value of input + 200 for propagation
167 |
168 | let txFee = UInt64(selectedUTXOs.count * satoshisForInput + outputs.count * 34 + 46 + 9 * tokenInputs + 9)
169 | var change: Int64 = Int64(totalAmount) - Int64(satoshisForTokens) - Int64(txFee)
170 |
171 | // If there is not enough gas, lets grab utxos from the wallet to refill
172 | if change < 0 {
173 | var sum = Int(change)
174 | let gasTokenUTXOs: [SLPWalletUTXO] = wallet.utxos
175 | .filter { utxo -> Bool in
176 | guard sum < 0
177 | , let privKey = wallet.getPrivKeyByCashAddress(utxo.cashAddress) else {
178 | return false
179 | }
180 | privKeys.append(privKey)
181 |
182 | sum = sum + Int(utxo.satoshis) - satoshisForInput // Minus the future fee for an input
183 | return true
184 | }
185 | .compactMap { $0 }
186 |
187 | let gasUTXOs = gasTokenUTXOs.map { utxo -> UnspentTransaction in
188 | return utxo.asUnspentTransaction()
189 | }
190 |
191 | let gas: Int64 = Int64(gasUTXOs.reduce(0) { $0 + $1.output.value })
192 |
193 | change = change + gas
194 |
195 | if change < 0 {
196 | // Throw exception not enough gas
197 | throw SLPTransactionBuilderError.GAS_INSUFFICIENT
198 | }
199 |
200 | // Add my gasUTXOs in my selectedUTXOs
201 | selectedUTXOs.append(contentsOf: gasUTXOs)
202 | usedUTXOs.append(contentsOf: gasTokenUTXOs)
203 | }
204 |
205 | let unsignedInputs = selectedUTXOs.map { TransactionInput(previousOutput: $0.outpoint, signatureScript: Data(), sequence: UInt32.max) }
206 |
207 | if change > minSatoshisForToken { // Minimum for expensable utxo
208 | let changeOutput = TransactionOutput(value: UInt64(change), lockingScript: lockScriptCashChange.data)
209 | outputs.append(changeOutput)
210 | }
211 |
212 | let tx = Transaction(version: 1, inputs: unsignedInputs, outputs: outputs, lockTime: 0)
213 | let unsignedTx = UnsignedTransaction(tx: tx, utxos: selectedUTXOs)
214 |
215 |
216 | var inputsToSign = unsignedTx.tx.inputs
217 | var transactionToSign: Transaction {
218 | return Transaction(version: unsignedTx.tx.version, inputs: inputsToSign, outputs: unsignedTx.tx.outputs, lockTime: unsignedTx.tx.lockTime)
219 | }
220 |
221 | // Signing
222 | let hashType = SighashType.BCH.ALL
223 | for (i, utxo) in unsignedTx.utxos.enumerated() {
224 | let sighash: Data = transactionToSign.signatureHash(for: utxo.output, inputIndex: i, hashType: SighashType.BCH.ALL)
225 | let signature: Data = try! Crypto.sign(sighash, privateKey: privKeys[i])
226 | let txin = inputsToSign[i]
227 | let pubkey = privKeys[i].publicKey()
228 |
229 | let unlockingScript = Script.buildPublicKeyUnlockingScript(signature: signature, pubkey: pubkey, hashType: hashType)
230 |
231 | inputsToSign[i] = TransactionInput(previousOutput: txin.previousOutput, signatureScript: unlockingScript, sequence: txin.sequence)
232 | }
233 |
234 | let signedTx = transactionToSign.serialized()
235 |
236 | //
237 | // Check Destination
238 | //
239 |
240 | if toAddress.cashaddr == tokenChangeAddress.cashaddr {
241 | let newUTXO = SLPTokenUTXO(unsignedTx.tx.txID, satoshis: Int64(minSatoshisForToken), cashAddress: tokenChangeAddress.cashaddr, scriptPubKey: lockScriptTo.hex, index: 1, rawTokenQty: rawTokenAmount)
242 | newUTXO._isValid = true
243 | newUTXOs.append(newUTXO)
244 | }
245 |
246 | if toAddress.cashaddr == cashChangeAddress.cashaddr {
247 | let newUTXO = SLPWalletUTXO(unsignedTx.tx.txID, satoshis: Int64(minSatoshisForToken), cashAddress: cashChangeAddress.cashaddr, scriptPubKey: lockScriptTo.hex, index: 1)
248 | newUTXOs.append(newUTXO)
249 | }
250 |
251 | //
252 | // Check Change
253 | //
254 |
255 | var index = 2
256 | if rawTokenChange > 0 {
257 | let newUTXO = SLPTokenUTXO(unsignedTx.tx.txID, satoshis: Int64(minSatoshisForToken), cashAddress: tokenChangeAddress.cashaddr, scriptPubKey: lockScriptTokenChange.hex, index: index, rawTokenQty: rawTokenChange)
258 | newUTXO._isValid = true
259 | newUTXOs.append(newUTXO)
260 | index += 1
261 | }
262 |
263 | if change > minSatoshisForToken { // Minimum for expensable utxo
264 | let newUTXO = SLPWalletUTXO(unsignedTx.tx.txID, satoshis: Int64(change), cashAddress: cashChangeAddress.cashaddr, scriptPubKey: lockScriptCashChange.hex, index: index)
265 | newUTXOs.append(newUTXO)
266 | }
267 |
268 | // Return rawTx, inputs used, new outputs
269 | return SLPTransactionBuilderResponse(rawTx: signedTx.hex, usedUTXOs: usedUTXOs, newUTXOs: newUTXOs)
270 | }
271 | }
272 |
--------------------------------------------------------------------------------
/SLPWallet/SLPWallet.swift:
--------------------------------------------------------------------------------
1 | //
2 | // SLPWallet.swift
3 | // SLPWallet
4 | //
5 | // Created by Jean-Baptiste Dominguez on 2019/02/27.
6 | // Copyright © 2019 Bitcoin.com. All rights reserved.
7 | //
8 |
9 | import BitcoinKit
10 | import RxSwift
11 | import RxCocoa
12 | import KeychainAccess
13 |
14 | public enum SLPWalletError : String, Error {
15 | case TOKEN_ID_REQUIRED
16 | case MNEMONIC_NOT_FOUND
17 | case PRIVKEY_NOT_FOUND
18 | }
19 |
20 | public protocol SLPWalletDelegate {
21 | func onUpdatedToken(_ token: SLPToken)
22 | }
23 |
24 | public class SLPWallet {
25 |
26 | fileprivate static let bag = DisposeBag()
27 | fileprivate static let storageProvider = SecureStorageProvider()
28 | fileprivate let semaphore = DispatchSemaphore(value: 1)
29 |
30 | let _mnemonic: [String]
31 | var _tokens: [String:SLPToken]
32 |
33 | let _BCHAccount: SLPWalletAccount
34 | let _SLPAccount: SLPWalletAccount
35 |
36 | let _slpAddress: String
37 |
38 | let _network: Network
39 | var _utxos: [SLPWalletUTXO]
40 |
41 | // Garbage
42 | var _usedUTXOs: [SLPWalletUTXO]
43 |
44 | var BCHAccount: SLPWalletAccount {
45 | get { return _BCHAccount }
46 | }
47 |
48 | var SLPAccount: SLPWalletAccount {
49 | get { return _SLPAccount }
50 | }
51 |
52 | public var utxos: [SLPWalletUTXO] {
53 | get { return _utxos }
54 | }
55 |
56 | public var mnemonic: [String] {
57 | get { return _mnemonic }
58 | }
59 | public var cashAddress: String {
60 | get { return _BCHAccount.cashAddress }
61 | }
62 | public var slpAddress: String {
63 | get { return _slpAddress }
64 | }
65 | public var tokens: [String:SLPToken] {
66 | get { return _tokens }
67 | }
68 |
69 | public var delegate: SLPWalletDelegate?
70 |
71 | public var schedulerInterval: Double = 20 {
72 | didSet {
73 | scheduler.cancel()
74 | scheduler.schedule(deadline: .now(), repeating: schedulerInterval)
75 | }
76 | }
77 | public lazy var scheduler: DispatchSourceTimer = {
78 | let t = DispatchSource.makeTimerSource()
79 | t.schedule(deadline: .now(), repeating: schedulerInterval)
80 | t.setEventHandler(handler: { [weak self] in
81 | self?.fetchTokens()
82 | .subscribe()
83 | .disposed(by: SLPWallet.bag)
84 | })
85 | return t
86 | }()
87 |
88 | public convenience init(_ network: Network) throws {
89 | try self.init(network, force: false)
90 | }
91 |
92 | public convenience init(_ network: Network, force: Bool = false) throws {
93 | if force {
94 | let mnemonic = try Mnemonic.generate()
95 | let mnemonicStr = mnemonic.joined(separator: " ")
96 | try self.init(mnemonicStr, network: network)
97 | } else {
98 | // Get in keychain
99 | guard let mnemonic = try SLPWallet.storageProvider.getString("mnemonic") else {
100 | try self.init(network, force: true)
101 | return
102 | }
103 | try self.init(mnemonic, network: network)
104 | }
105 | }
106 |
107 | public init(_ mnemonic: String, network: Network) throws {
108 |
109 | // Store in keychain
110 | try SLPWallet.storageProvider.setString(mnemonic, key: "mnemonic")
111 |
112 | // Then go forward
113 | let arrayOfwords = mnemonic.components(separatedBy: " ")
114 |
115 | let seed = Mnemonic.seed(mnemonic: mnemonic.components(separatedBy: " "))
116 | let hdPrivKey = HDPrivateKey(seed: seed, network: network)
117 |
118 | // 145
119 | var xPrivKey = try! hdPrivKey.derived(at: 44, hardened: true).derived(at: 145, hardened: true).derived(at: 0, hardened: true)
120 | var privKey = try! xPrivKey.derived(at: UInt32(0)).derived(at: UInt32(0)).privateKey()
121 |
122 | self._BCHAccount = SLPWalletAccount(privKey: privKey, cashAddress: privKey.publicKey().toCashaddr().cashaddr)
123 |
124 | // 245
125 | xPrivKey = try! hdPrivKey.derived(at: 44, hardened: true).derived(at: 245, hardened: true).derived(at: 0, hardened: true)
126 | privKey = try! xPrivKey.derived(at: UInt32(0)).derived(at: UInt32(0)).privateKey()
127 |
128 | self._SLPAccount = SLPWalletAccount(privKey: privKey, cashAddress: privKey.publicKey().toCashaddr().cashaddr)
129 |
130 | self._mnemonic = arrayOfwords
131 | self._network = network
132 |
133 | self._slpAddress = privKey.publicKey().toSlpaddr().slpaddr
134 | self._tokens = [String:SLPToken]()
135 | self._utxos = [SLPWalletUTXO]()
136 | self._usedUTXOs = [SLPWalletUTXO]()
137 | }
138 | }
139 |
140 | public extension SLPWallet {
141 |
142 | func getGas() -> Int {
143 | return _utxos.reduce(0, { $0 + Int($1.satoshis) })
144 | }
145 |
146 | func fetchTokens() -> Single<[String:SLPToken]> {
147 |
148 | let cashAddresses = [BCHAccount.cashAddress, SLPAccount.cashAddress]
149 |
150 | return Single<[String:SLPToken]>.create { single in
151 | RestService
152 | .fetchUTXOs(cashAddresses)
153 | .subscribe { event in
154 | switch event {
155 | case .success(let rawUtxos):
156 | let utxos = rawUtxos
157 | .flatMap { $0.utxos }
158 |
159 | var myUTXOs: [String] = self.tokens
160 | .flatMap { $1._utxos }
161 | .compactMap { "\($0.txid)-\($0.index)" }
162 | myUTXOs.append(contentsOf: self.utxos.compactMap { "\($0.txid)-\($0.index)" })
163 |
164 | let requests = utxos
165 | .filter { !myUTXOs.contains("\($0.txid)-\($0.vout)") }
166 | .compactMap { $0.txid }
167 | .removeDuplicates()
168 | .chunk(20)
169 |
170 | guard requests.count > 0 else {
171 | single(.success(self._tokens))
172 | return
173 | }
174 |
175 | let observable = Observable
176 | .from(requests)
177 | .flatMap { request in
178 | Observable.zip(
179 | RestService.fetchTxDetails(request).asObservable()
180 | , RestService.fetchTxValidations(request).asObservable()
181 | , resultSelector: { (txs, validations) in
182 | return txs
183 | .enumerated()
184 | .compactMap { (index, tx) in
185 | return (tx, validations[index].valid)
186 | }
187 | })
188 | }
189 |
190 | observable
191 | .subscribe { event in
192 | switch event {
193 | case .next(let txs):
194 |
195 | self.semaphore.wait()
196 |
197 | var updatedTokens = [String:SLPToken]()
198 | var updatedUTXOs = [SLPWalletUTXO]()
199 |
200 | txs.forEach { (tx, isValid) in
201 |
202 | // Get the vouts that we are interested in
203 | let vouts = utxos
204 | .filter { $0.txid == tx.txid }
205 | .map { $0.vout }
206 |
207 | // Parse tx
208 | guard let parsedData = SLPTransactionParser.parse(tx, vouts: vouts) else {
209 | return
210 | }
211 |
212 | if let tokenId = parsedData.token.tokenId {
213 |
214 | // Validate the utxos if it should be
215 | parsedData.token._utxos = parsedData.token._utxos.filter { !self._usedUTXOs.contains($0) }
216 |
217 | // I don't remove it to avoid flickering, in case the API doesn't answer well
218 | parsedData.token._utxos.forEach { $0._isValid = isValid }
219 |
220 | if let token = updatedTokens[tokenId] {
221 | token.merge(parsedData.token)
222 | } else {
223 | updatedTokens[tokenId] = parsedData.token
224 | }
225 | }
226 |
227 | let newUtxos = parsedData.utxos.filter { !self._usedUTXOs.contains($0) }
228 | updatedUTXOs.append(contentsOf: newUtxos)
229 | }
230 |
231 | //
232 | //
233 | // Parse finished
234 | // Update data
235 | //
236 | //
237 |
238 | // Update the UTXOs used as gas :)
239 | self._utxos.mergeElements(newElements: updatedUTXOs)
240 |
241 | // Check which one is new and need to get the info from Genesis
242 | var newTokens = [SLPToken]()
243 | var tokensHaveChanged = [SLPToken]()
244 |
245 | updatedTokens.forEach { tokenId, token in
246 | guard let t = self._tokens[tokenId] else {
247 | if token._utxos.count > 0 {
248 | newTokens.append(token)
249 | }
250 | return
251 | }
252 |
253 | var hasChanged = false
254 | if t._utxos.count != token._utxos.count {
255 | hasChanged = true
256 | } else {
257 | let hash1 = t._utxos
258 | .sorted(by: { (u1, u2) -> Bool in
259 | return u1.txid < u2.txid && u1.index < u2.index
260 | })
261 | .compactMap { "\($0.hashValue)" }
262 | .joined(separator: "")
263 |
264 | let hash2 = token._utxos
265 | .sorted(by: { (u1, u2) -> Bool in
266 | return u1.txid < u2.txid && u1.index < u2.index
267 | })
268 | .compactMap { "\($0.hashValue)" }
269 | .joined(separator: "")
270 |
271 | if hash1 != hash2 {
272 | hasChanged = true
273 | }
274 | }
275 |
276 | // If it has changed, notify
277 | if hasChanged {
278 | t._utxos.mergeElements(newElements: token._utxos)
279 | tokensHaveChanged.append(t)
280 | }
281 | }
282 |
283 | // Notify changed tokens
284 | tokensHaveChanged.forEach { self.delegate?.onUpdatedToken($0) }
285 |
286 | self.semaphore.signal()
287 |
288 | //
289 | //
290 | // Update data finished
291 | // Get info on unknown tokens
292 | //
293 | //
294 |
295 | Observable
296 | .zip(newTokens.map { self.addToken($0).asObservable() })
297 | .subscribe { event in
298 | switch event {
299 | case .next(let tokens):
300 | // Notify new tokens
301 | tokens.forEach { self.delegate?.onUpdatedToken($0) }
302 | case .completed:
303 | single(.success(self._tokens))
304 | case .error(let error):
305 | single(.error(error))
306 | }
307 | }
308 | .disposed(by: SLPWallet.bag)
309 |
310 | case .error(let error):
311 | single(.error(error))
312 | case .completed: break
313 | }
314 | }
315 | .disposed(by: SLPWallet.bag)
316 | case .error(let error):
317 | single(.error(error))
318 | }
319 | }
320 | .disposed(by: SLPWallet.bag)
321 | return Disposables.create()
322 | }
323 | }
324 |
325 | func sendToken(_ tokenId: String, amount: Double, toAddress: String) -> Single {
326 | return Single.create { single in
327 | self.fetchTokens()
328 | .subscribe(onSuccess: { _ in
329 | do {
330 | let value = try SLPTransactionBuilder.build(self, tokenId: tokenId, amount: amount, toAddress: toAddress)
331 | RestService
332 | .broadcast(value.rawTx)
333 | .subscribe { response in
334 | switch response {
335 | case.success(let txid):
336 |
337 | guard let token = self.tokens[tokenId] else {
338 | return single(.success(txid))
339 | }
340 |
341 | // TODO: Debug why the TXID is wrong in the builder
342 | // Add the right txid
343 | value.newUTXOs.forEach { $0._txid = txid }
344 |
345 | self.updateUTXOsAfterSending(token, usedUTXOs: value.usedUTXOs, newUTXOs: value.newUTXOs)
346 |
347 | // Update delegate
348 | self.delegate?.onUpdatedToken(token)
349 |
350 | single(.success(txid))
351 | case .error(let error):
352 | single(.error(error))
353 | }
354 | }
355 | .disposed(by: SLPWallet.bag)
356 | } catch (let error) {
357 | single(.error(error))
358 | }
359 | }, onError: { error in
360 | single(.error(error))
361 | })
362 | .disposed(by: SLPWallet.bag)
363 |
364 | return Disposables.create()
365 | }
366 | }
367 |
368 | func addToken(_ token: SLPToken) -> Single {
369 | return Single.create { single in
370 | guard let tokenId = token.tokenId else {
371 | single(.error(SLPWalletError.TOKEN_ID_REQUIRED))
372 | return Disposables.create()
373 | }
374 | RestService
375 | .fetchTxDetails([tokenId])
376 | .subscribe { response in
377 | switch response {
378 | case.success(let txs):
379 | txs.forEach { tx in
380 |
381 | // Parse tx
382 | guard let parsedData = SLPTransactionParser.parse(tx, vouts: []) else {
383 | return
384 | }
385 |
386 | if let _ = parsedData.token.tokenId {
387 | token.merge(parsedData.token)
388 | }
389 | }
390 |
391 | // Add the token in the list
392 | self._tokens[tokenId] = token
393 |
394 | single(.success(token))
395 | case .error(let error):
396 | single(.error(error))
397 | }
398 | }
399 | .disposed(by: SLPWallet.bag)
400 | return Disposables.create()
401 | }
402 | }
403 | }
404 |
405 | extension SLPWallet {
406 | func getPrivKeyByCashAddress(_ cashAddress: String) -> PrivateKey? {
407 | switch cashAddress {
408 | case BCHAccount.cashAddress:
409 | return BCHAccount.privKey
410 | case SLPAccount.cashAddress:
411 | return SLPAccount.privKey
412 | default:
413 | return nil
414 | }
415 | }
416 |
417 | func updateUTXOsAfterSending(_ token: SLPToken, usedUTXOs: [SLPWalletUTXO], newUTXOs: [SLPWalletUTXO]) {
418 | // Add a lock to be sure I am not adding or removing in the same time with the fetchTokens
419 | semaphore.wait()
420 |
421 | newUTXOs.forEach { UTXO in
422 | guard let newUTXO = UTXO as? SLPTokenUTXO else {
423 | return self.addUTXO(UTXO)
424 | }
425 | return token.addUTXO(newUTXO)
426 | }
427 |
428 | _usedUTXOs.append(contentsOf: usedUTXOs)
429 | usedUTXOs.forEach { UTXO in
430 | guard let newUTXO = UTXO as? SLPTokenUTXO else {
431 | return self.removeUTXO(UTXO)
432 | }
433 | return token.removeUTXO(newUTXO)
434 | }
435 |
436 | semaphore.signal()
437 | }
438 |
439 | func addUTXO(_ utxo: SLPWalletUTXO) {
440 | _utxos.append(utxo)
441 | }
442 |
443 | func removeUTXO(_ utxo: SLPWalletUTXO) {
444 | guard let i = _utxos.firstIndex(where: { $0.index == utxo.index && $0.txid == utxo.txid }) else {
445 | return
446 | }
447 | _utxos.remove(at: i)
448 | }
449 | }
450 |
--------------------------------------------------------------------------------
/SLPWallet.xcodeproj/project.pbxproj:
--------------------------------------------------------------------------------
1 | // !$*UTF8*$!
2 | {
3 | archiveVersion = 1;
4 | classes = {
5 | };
6 | objectVersion = 51;
7 | objects = {
8 |
9 | /* Begin PBXBuildFile section */
10 | 20F9189D876A537C9921C41D /* Pods_All_SLPWallet.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 3D8FBE6A85C0A5C87BA47239 /* Pods_All_SLPWallet.framework */; };
11 | 28F506C21B69A85C511D3BEF /* Pods_SLPWalletTests.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 7BF528C674BCE9EEB5E90520 /* Pods_SLPWalletTests.framework */; };
12 | 3B288C49222C387700C1AC81 /* TokenQtyConverter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3B288C46222C300E00C1AC81 /* TokenQtyConverter.swift */; };
13 | 3B6D8278224241B000119AF0 /* SLPWalletAccount.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3B6D8277224241B000119AF0 /* SLPWalletAccount.swift */; };
14 | 3B79D223223B160100FF3AFB /* Double+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3B79D222223B160100FF3AFB /* Double+Extensions.swift */; };
15 | 3B7FE8D52226A67E00CB5755 /* Data+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3B7FE8D42226A67E00CB5755 /* Data+Extensions.swift */; };
16 | 3B7FE8FE2227ABD900CB5755 /* SLPWalletTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3B7FE8FD2227ABD900CB5755 /* SLPWalletTest.swift */; };
17 | 3B7FE9002227BF8300CB5755 /* String+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3B7FE8FF2227BF8300CB5755 /* String+Extensions.swift */; };
18 | 3B7FE90722283C2B00CB5755 /* SLPWallet.podspec in Resources */ = {isa = PBXBuildFile; fileRef = 3B7FE90622283C2B00CB5755 /* SLPWallet.podspec */; };
19 | 3B7FE9292229225200CB5755 /* SLPTransactionParser.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3B7FE9282229225200CB5755 /* SLPTransactionParser.swift */; };
20 | 3B7FE92B2229892F00CB5755 /* README.md in Resources */ = {isa = PBXBuildFile; fileRef = 3B7FE92A2229892E00CB5755 /* README.md */; };
21 | 3B7FE92D222990FB00CB5755 /* SLPToken.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3B7FE92C222990FB00CB5755 /* SLPToken.swift */; };
22 | 3B7FE92F2229913100CB5755 /* SLPWalletUTXO.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3B7FE92E2229913100CB5755 /* SLPWalletUTXO.swift */; };
23 | 3B7FE9312229915600CB5755 /* SLPTokenUTXO.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3B7FE9302229915600CB5755 /* SLPTokenUTXO.swift */; };
24 | 3B7FE933222A895E00CB5755 /* Array+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3B7FE932222A895E00CB5755 /* Array+Extensions.swift */; };
25 | 3B99B05F2226251B00A1B599 /* SLPWallet.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 3B99B0552226251B00A1B599 /* SLPWallet.framework */; };
26 | 3B99B07E2226290100A1B599 /* UserDefaults+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3B99B0722226290100A1B599 /* UserDefaults+Extensions.swift */; };
27 | 3B99B0802226290100A1B599 /* RestService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3B99B0762226290100A1B599 /* RestService.swift */; };
28 | 3B99B0812226290100A1B599 /* RestNetwork.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3B99B0782226290100A1B599 /* RestNetwork.swift */; };
29 | 3B99B08B22262DC700A1B599 /* SLPWallet.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3B99B08A22262DC700A1B599 /* SLPWallet.swift */; };
30 | 3BB10CB1224B59E6009BE56D /* SLPWalletConfig.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3BB10CB0224B59E6009BE56D /* SLPWalletConfig.swift */; };
31 | 3BB10CB3224B5AB8009BE56D /* StorageProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3BB10CB2224B5AB8009BE56D /* StorageProvider.swift */; };
32 | 3BB10CB5224B5ADB009BE56D /* InternalStorageProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3BB10CB4224B5ADB009BE56D /* InternalStorageProvider.swift */; };
33 | 3BB10CB8224B6AE2009BE56D /* SLPWalletConfigTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3BB10CB7224B6AE2009BE56D /* SLPWalletConfigTest.swift */; };
34 | 3BB10CBA224B6CC7009BE56D /* InternalStorageProviderTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3BB10CB9224B6CC6009BE56D /* InternalStorageProviderTest.swift */; };
35 | 3BB10CBC224B6CE7009BE56D /* SecureStorageProviderTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3BB10CBB224B6CE7009BE56D /* SecureStorageProviderTest.swift */; };
36 | 3BC2880B222D022F00EB3375 /* SLPTransactionBuilder.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3BC2880A222D022F00EB3375 /* SLPTransactionBuilder.swift */; };
37 | 3BD622602248943300503956 /* SLPTransactionParserTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3BD6225F2248943300503956 /* SLPTransactionParserTest.swift */; };
38 | 3BD6226722489C6100503956 /* tx_details_genesis_tst.json in Resources */ = {isa = PBXBuildFile; fileRef = 3BD6226622489C6000503956 /* tx_details_genesis_tst.json */; };
39 | 3BD622692248B05900503956 /* tx_details_send_tst.json in Resources */ = {isa = PBXBuildFile; fileRef = 3BD622682248B05900503956 /* tx_details_send_tst.json */; };
40 | 3BD6226B2248B5F500503956 /* tx_details_mint_lvl001.json in Resources */ = {isa = PBXBuildFile; fileRef = 3BD6226A2248B5F500503956 /* tx_details_mint_lvl001.json */; };
41 | 3BD7508522358D9A00CF5072 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3BD7508422358D9A00CF5072 /* AppDelegate.swift */; };
42 | 3BD7508722358D9A00CF5072 /* ViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3BD7508622358D9A00CF5072 /* ViewController.swift */; };
43 | 3BD7508A22358D9A00CF5072 /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 3BD7508822358D9A00CF5072 /* Main.storyboard */; };
44 | 3BD7508C22358D9A00CF5072 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 3BD7508B22358D9A00CF5072 /* Assets.xcassets */; };
45 | 3BD7508F22358D9A00CF5072 /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 3BD7508D22358D9A00CF5072 /* LaunchScreen.storyboard */; };
46 | 3BD750992237B0E400CF5072 /* TokenQtyConverterTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3BD750982237B0E400CF5072 /* TokenQtyConverterTest.swift */; };
47 | 3BD7509B2237B5AF00CF5072 /* SLPWalletUTXOTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3BD7509A2237B5AF00CF5072 /* SLPWalletUTXOTest.swift */; };
48 | 3BD7509D2237BEAB00CF5072 /* Double+ExtensionsTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3BD7509C2237BEAB00CF5072 /* Double+ExtensionsTest.swift */; };
49 | 3BD7509F2237BF9000CF5072 /* String+ExtensionsTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3BD7509E2237BF9000CF5072 /* String+ExtensionsTest.swift */; };
50 | 3BD750A12237C06D00CF5072 /* SLPTokenTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3BD750A02237C06D00CF5072 /* SLPTokenTest.swift */; };
51 | 3BD750A32237C3E800CF5072 /* UserDefault+ExtensionsTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3BD750A22237C3E800CF5072 /* UserDefault+ExtensionsTest.swift */; };
52 | 3BD750A52237C72900CF5072 /* SLPTransactionBuilderTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3BD750A42237C72900CF5072 /* SLPTransactionBuilderTest.swift */; };
53 | 3BD750AF2237E93400CF5072 /* SecureStorageProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3BD750AE2237E93400CF5072 /* SecureStorageProvider.swift */; };
54 | 3BDFC1572233A91E00DC167E /* SLPWalletTests.entitlements in Sources */ = {isa = PBXBuildFile; fileRef = 3BDFC1562233A91E00DC167E /* SLPWalletTests.entitlements */; };
55 | 3BE242AC22266165003432CE /* Podfile in Resources */ = {isa = PBXBuildFile; fileRef = 3BE242AB22266165003432CE /* Podfile */; };
56 | 3BE242AE222667F2003432CE /* RestServiceTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3BE242AD222667F2003432CE /* RestServiceTest.swift */; };
57 | /* End PBXBuildFile section */
58 |
59 | /* Begin PBXContainerItemProxy section */
60 | 3B99B0602226251B00A1B599 /* PBXContainerItemProxy */ = {
61 | isa = PBXContainerItemProxy;
62 | containerPortal = 3B99B04C2226251B00A1B599 /* Project object */;
63 | proxyType = 1;
64 | remoteGlobalIDString = 3B99B0542226251B00A1B599;
65 | remoteInfo = SLPWallet;
66 | };
67 | 3BD7509422358DA400CF5072 /* PBXContainerItemProxy */ = {
68 | isa = PBXContainerItemProxy;
69 | containerPortal = 3B99B04C2226251B00A1B599 /* Project object */;
70 | proxyType = 1;
71 | remoteGlobalIDString = 3BD7508122358D9A00CF5072;
72 | remoteInfo = SLPWalletHostTests;
73 | };
74 | /* End PBXContainerItemProxy section */
75 |
76 | /* Begin PBXFileReference section */
77 | 1DFFAD2AFD166179BF17DA9E /* Pods-SLPWalletTests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-SLPWalletTests.release.xcconfig"; path = "Target Support Files/Pods-SLPWalletTests/Pods-SLPWalletTests.release.xcconfig"; sourceTree = ""; };
78 | 3B288C46222C300E00C1AC81 /* TokenQtyConverter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TokenQtyConverter.swift; sourceTree = ""; };
79 | 3B6D8277224241B000119AF0 /* SLPWalletAccount.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SLPWalletAccount.swift; sourceTree = ""; };
80 | 3B79D222223B160100FF3AFB /* Double+Extensions.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "Double+Extensions.swift"; sourceTree = ""; };
81 | 3B7FE8D42226A67E00CB5755 /* Data+Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Data+Extensions.swift"; sourceTree = ""; };
82 | 3B7FE8FD2227ABD900CB5755 /* SLPWalletTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SLPWalletTest.swift; sourceTree = ""; };
83 | 3B7FE8FF2227BF8300CB5755 /* String+Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "String+Extensions.swift"; sourceTree = ""; };
84 | 3B7FE90622283C2B00CB5755 /* SLPWallet.podspec */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = SLPWallet.podspec; sourceTree = ""; };
85 | 3B7FE9282229225200CB5755 /* SLPTransactionParser.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SLPTransactionParser.swift; sourceTree = ""; };
86 | 3B7FE92A2229892E00CB5755 /* README.md */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = net.daringfireball.markdown; path = README.md; sourceTree = ""; };
87 | 3B7FE92C222990FB00CB5755 /* SLPToken.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SLPToken.swift; sourceTree = ""; };
88 | 3B7FE92E2229913100CB5755 /* SLPWalletUTXO.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SLPWalletUTXO.swift; sourceTree = ""; };
89 | 3B7FE9302229915600CB5755 /* SLPTokenUTXO.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SLPTokenUTXO.swift; sourceTree = ""; };
90 | 3B7FE932222A895E00CB5755 /* Array+Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Array+Extensions.swift"; sourceTree = ""; };
91 | 3B99B0552226251B00A1B599 /* SLPWallet.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = SLPWallet.framework; sourceTree = BUILT_PRODUCTS_DIR; };
92 | 3B99B0592226251B00A1B599 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; };
93 | 3B99B05E2226251B00A1B599 /* SLPWalletTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = SLPWalletTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
94 | 3B99B0652226251B00A1B599 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; };
95 | 3B99B0722226290100A1B599 /* UserDefaults+Extensions.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "UserDefaults+Extensions.swift"; sourceTree = ""; };
96 | 3B99B0762226290100A1B599 /* RestService.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RestService.swift; sourceTree = ""; };
97 | 3B99B0782226290100A1B599 /* RestNetwork.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RestNetwork.swift; sourceTree = ""; };
98 | 3B99B08A22262DC700A1B599 /* SLPWallet.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SLPWallet.swift; sourceTree = ""; };
99 | 3BB10CB0224B59E6009BE56D /* SLPWalletConfig.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SLPWalletConfig.swift; sourceTree = ""; };
100 | 3BB10CB2224B5AB8009BE56D /* StorageProvider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StorageProvider.swift; sourceTree = ""; };
101 | 3BB10CB4224B5ADB009BE56D /* InternalStorageProvider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InternalStorageProvider.swift; sourceTree = ""; };
102 | 3BB10CB7224B6AE2009BE56D /* SLPWalletConfigTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SLPWalletConfigTest.swift; sourceTree = ""; };
103 | 3BB10CB9224B6CC6009BE56D /* InternalStorageProviderTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InternalStorageProviderTest.swift; sourceTree = ""; };
104 | 3BB10CBB224B6CE7009BE56D /* SecureStorageProviderTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SecureStorageProviderTest.swift; sourceTree = ""; };
105 | 3BC2880A222D022F00EB3375 /* SLPTransactionBuilder.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SLPTransactionBuilder.swift; sourceTree = ""; };
106 | 3BD6225F2248943300503956 /* SLPTransactionParserTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SLPTransactionParserTest.swift; sourceTree = ""; };
107 | 3BD6226622489C6000503956 /* tx_details_genesis_tst.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = tx_details_genesis_tst.json; sourceTree = ""; };
108 | 3BD622682248B05900503956 /* tx_details_send_tst.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = tx_details_send_tst.json; sourceTree = ""; };
109 | 3BD6226A2248B5F500503956 /* tx_details_mint_lvl001.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = tx_details_mint_lvl001.json; sourceTree = ""; };
110 | 3BD7504E2235745D00CF5072 /* BitcoinKit.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = BitcoinKit.framework; sourceTree = BUILT_PRODUCTS_DIR; };
111 | 3BD7508222358D9A00CF5072 /* SLPWalletHostTests.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = SLPWalletHostTests.app; sourceTree = BUILT_PRODUCTS_DIR; };
112 | 3BD7508422358D9A00CF5072 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; };
113 | 3BD7508622358D9A00CF5072 /* ViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ViewController.swift; sourceTree = ""; };
114 | 3BD7508922358D9A00CF5072 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; };
115 | 3BD7508B22358D9A00CF5072 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; };
116 | 3BD7508E22358D9A00CF5072 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; };
117 | 3BD7509022358D9A00CF5072 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; };
118 | 3BD750982237B0E400CF5072 /* TokenQtyConverterTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TokenQtyConverterTest.swift; sourceTree = ""; };
119 | 3BD7509A2237B5AF00CF5072 /* SLPWalletUTXOTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SLPWalletUTXOTest.swift; sourceTree = ""; };
120 | 3BD7509C2237BEAB00CF5072 /* Double+ExtensionsTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Double+ExtensionsTest.swift"; sourceTree = ""; };
121 | 3BD7509E2237BF9000CF5072 /* String+ExtensionsTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "String+ExtensionsTest.swift"; sourceTree = ""; };
122 | 3BD750A02237C06D00CF5072 /* SLPTokenTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SLPTokenTest.swift; sourceTree = ""; };
123 | 3BD750A22237C3E800CF5072 /* UserDefault+ExtensionsTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UserDefault+ExtensionsTest.swift"; sourceTree = ""; };
124 | 3BD750A42237C72900CF5072 /* SLPTransactionBuilderTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SLPTransactionBuilderTest.swift; sourceTree = ""; };
125 | 3BD750AE2237E93400CF5072 /* SecureStorageProvider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SecureStorageProvider.swift; sourceTree = ""; };
126 | 3BDFC1562233A91E00DC167E /* SLPWalletTests.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = SLPWalletTests.entitlements; sourceTree = ""; };
127 | 3BE242AB22266165003432CE /* Podfile */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = Podfile; sourceTree = ""; xcLanguageSpecificationIdentifier = xcode.lang.ruby; };
128 | 3BE242AD222667F2003432CE /* RestServiceTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RestServiceTest.swift; sourceTree = ""; };
129 | 3D8FBE6A85C0A5C87BA47239 /* Pods_All_SLPWallet.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_All_SLPWallet.framework; sourceTree = BUILT_PRODUCTS_DIR; };
130 | 7BF528C674BCE9EEB5E90520 /* Pods_SLPWalletTests.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_SLPWalletTests.framework; sourceTree = BUILT_PRODUCTS_DIR; };
131 | 890110000F200FAF616C6E2E /* Pods-All-SLPWallet.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-All-SLPWallet.debug.xcconfig"; path = "Target Support Files/Pods-All-SLPWallet/Pods-All-SLPWallet.debug.xcconfig"; sourceTree = ""; };
132 | F1DBD6941A749C2400954E1F /* Pods-SLPWalletTests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-SLPWalletTests.debug.xcconfig"; path = "Target Support Files/Pods-SLPWalletTests/Pods-SLPWalletTests.debug.xcconfig"; sourceTree = ""; };
133 | FB3A348EC237FAE26363CAC1 /* Pods-All-SLPWallet.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-All-SLPWallet.release.xcconfig"; path = "Target Support Files/Pods-All-SLPWallet/Pods-All-SLPWallet.release.xcconfig"; sourceTree = ""; };
134 | /* End PBXFileReference section */
135 |
136 | /* Begin PBXFrameworksBuildPhase section */
137 | 3B99B0522226251B00A1B599 /* Frameworks */ = {
138 | isa = PBXFrameworksBuildPhase;
139 | buildActionMask = 2147483647;
140 | files = (
141 | 20F9189D876A537C9921C41D /* Pods_All_SLPWallet.framework in Frameworks */,
142 | );
143 | runOnlyForDeploymentPostprocessing = 0;
144 | };
145 | 3B99B05B2226251B00A1B599 /* Frameworks */ = {
146 | isa = PBXFrameworksBuildPhase;
147 | buildActionMask = 2147483647;
148 | files = (
149 | 3B99B05F2226251B00A1B599 /* SLPWallet.framework in Frameworks */,
150 | 28F506C21B69A85C511D3BEF /* Pods_SLPWalletTests.framework in Frameworks */,
151 | );
152 | runOnlyForDeploymentPostprocessing = 0;
153 | };
154 | 3BD7507F22358D9A00CF5072 /* Frameworks */ = {
155 | isa = PBXFrameworksBuildPhase;
156 | buildActionMask = 2147483647;
157 | files = (
158 | );
159 | runOnlyForDeploymentPostprocessing = 0;
160 | };
161 | /* End PBXFrameworksBuildPhase section */
162 |
163 | /* Begin PBXGroup section */
164 | 0BD85E2FC2EB379579C59110 /* Frameworks */ = {
165 | isa = PBXGroup;
166 | children = (
167 | 3BD7504E2235745D00CF5072 /* BitcoinKit.framework */,
168 | 3D8FBE6A85C0A5C87BA47239 /* Pods_All_SLPWallet.framework */,
169 | 7BF528C674BCE9EEB5E90520 /* Pods_SLPWalletTests.framework */,
170 | );
171 | name = Frameworks;
172 | sourceTree = "";
173 | };
174 | 1BE36C2D73396B1F26487CBB /* Pods */ = {
175 | isa = PBXGroup;
176 | children = (
177 | 890110000F200FAF616C6E2E /* Pods-All-SLPWallet.debug.xcconfig */,
178 | FB3A348EC237FAE26363CAC1 /* Pods-All-SLPWallet.release.xcconfig */,
179 | F1DBD6941A749C2400954E1F /* Pods-SLPWalletTests.debug.xcconfig */,
180 | 1DFFAD2AFD166179BF17DA9E /* Pods-SLPWalletTests.release.xcconfig */,
181 | );
182 | path = Pods;
183 | sourceTree = "";
184 | };
185 | 3B288C4A222C388500C1AC81 /* Utils */ = {
186 | isa = PBXGroup;
187 | children = (
188 | 3BC2880A222D022F00EB3375 /* SLPTransactionBuilder.swift */,
189 | 3B7FE9282229225200CB5755 /* SLPTransactionParser.swift */,
190 | 3B288C46222C300E00C1AC81 /* TokenQtyConverter.swift */,
191 | );
192 | path = Utils;
193 | sourceTree = "";
194 | };
195 | 3B99B04B2226251B00A1B599 = {
196 | isa = PBXGroup;
197 | children = (
198 | 3B7FE92A2229892E00CB5755 /* README.md */,
199 | 3B7FE90622283C2B00CB5755 /* SLPWallet.podspec */,
200 | 3BE242AB22266165003432CE /* Podfile */,
201 | 3B99B0572226251B00A1B599 /* SLPWallet */,
202 | 3B99B0622226251B00A1B599 /* SLPWalletTests */,
203 | 3BD7508322358D9A00CF5072 /* SLPWalletHostTests */,
204 | 3B99B0562226251B00A1B599 /* Products */,
205 | 0BD85E2FC2EB379579C59110 /* Frameworks */,
206 | 1BE36C2D73396B1F26487CBB /* Pods */,
207 | );
208 | sourceTree = "";
209 | };
210 | 3B99B0562226251B00A1B599 /* Products */ = {
211 | isa = PBXGroup;
212 | children = (
213 | 3B99B0552226251B00A1B599 /* SLPWallet.framework */,
214 | 3B99B05E2226251B00A1B599 /* SLPWalletTests.xctest */,
215 | 3BD7508222358D9A00CF5072 /* SLPWalletHostTests.app */,
216 | );
217 | name = Products;
218 | sourceTree = "";
219 | };
220 | 3B99B0572226251B00A1B599 /* SLPWallet */ = {
221 | isa = PBXGroup;
222 | children = (
223 | 3BB10CB6224B5BCD009BE56D /* StorageProvider */,
224 | 3BB10CB2224B5AB8009BE56D /* StorageProvider.swift */,
225 | 3BB10CBE224B76F5009BE56D /* Token */,
226 | 3BB10CBD224B76DA009BE56D /* Wallet */,
227 | 3B99B08A22262DC700A1B599 /* SLPWallet.swift */,
228 | 3BB10CB0224B59E6009BE56D /* SLPWalletConfig.swift */,
229 | 3B288C4A222C388500C1AC81 /* Utils */,
230 | 3B99B06F2226290100A1B599 /* Extensions */,
231 | 3B99B0772226290100A1B599 /* Networks */,
232 | 3B99B0752226290100A1B599 /* Services */,
233 | 3B99B0592226251B00A1B599 /* Info.plist */,
234 | );
235 | path = SLPWallet;
236 | sourceTree = "";
237 | };
238 | 3B99B0622226251B00A1B599 /* SLPWalletTests */ = {
239 | isa = PBXGroup;
240 | children = (
241 | 3BB10C472248B621009BE56D /* Assets */,
242 | 3BE242AD222667F2003432CE /* RestServiceTest.swift */,
243 | 3B7FE8FD2227ABD900CB5755 /* SLPWalletTest.swift */,
244 | 3BD750A02237C06D00CF5072 /* SLPTokenTest.swift */,
245 | 3BD7509A2237B5AF00CF5072 /* SLPWalletUTXOTest.swift */,
246 | 3BB10CB7224B6AE2009BE56D /* SLPWalletConfigTest.swift */,
247 | 3BD6225F2248943300503956 /* SLPTransactionParserTest.swift */,
248 | 3BD750A42237C72900CF5072 /* SLPTransactionBuilderTest.swift */,
249 | 3BB10CBB224B6CE7009BE56D /* SecureStorageProviderTest.swift */,
250 | 3BB10CB9224B6CC6009BE56D /* InternalStorageProviderTest.swift */,
251 | 3BD750982237B0E400CF5072 /* TokenQtyConverterTest.swift */,
252 | 3BD7509C2237BEAB00CF5072 /* Double+ExtensionsTest.swift */,
253 | 3BD7509E2237BF9000CF5072 /* String+ExtensionsTest.swift */,
254 | 3BD750A22237C3E800CF5072 /* UserDefault+ExtensionsTest.swift */,
255 | 3B99B0652226251B00A1B599 /* Info.plist */,
256 | 3BDFC1562233A91E00DC167E /* SLPWalletTests.entitlements */,
257 | );
258 | path = SLPWalletTests;
259 | sourceTree = "";
260 | };
261 | 3B99B06F2226290100A1B599 /* Extensions */ = {
262 | isa = PBXGroup;
263 | children = (
264 | 3B79D222223B160100FF3AFB /* Double+Extensions.swift */,
265 | 3B7FE8D42226A67E00CB5755 /* Data+Extensions.swift */,
266 | 3B99B0722226290100A1B599 /* UserDefaults+Extensions.swift */,
267 | 3B7FE8FF2227BF8300CB5755 /* String+Extensions.swift */,
268 | 3B7FE932222A895E00CB5755 /* Array+Extensions.swift */,
269 | );
270 | path = Extensions;
271 | sourceTree = "";
272 | };
273 | 3B99B0752226290100A1B599 /* Services */ = {
274 | isa = PBXGroup;
275 | children = (
276 | 3B99B0762226290100A1B599 /* RestService.swift */,
277 | );
278 | path = Services;
279 | sourceTree = "";
280 | };
281 | 3B99B0772226290100A1B599 /* Networks */ = {
282 | isa = PBXGroup;
283 | children = (
284 | 3B99B0782226290100A1B599 /* RestNetwork.swift */,
285 | );
286 | path = Networks;
287 | sourceTree = "";
288 | };
289 | 3BB10C472248B621009BE56D /* Assets */ = {
290 | isa = PBXGroup;
291 | children = (
292 | 3BD6226A2248B5F500503956 /* tx_details_mint_lvl001.json */,
293 | 3BD622682248B05900503956 /* tx_details_send_tst.json */,
294 | 3BD6226622489C6000503956 /* tx_details_genesis_tst.json */,
295 | );
296 | path = Assets;
297 | sourceTree = "";
298 | };
299 | 3BB10CB6224B5BCD009BE56D /* StorageProvider */ = {
300 | isa = PBXGroup;
301 | children = (
302 | 3BD750AE2237E93400CF5072 /* SecureStorageProvider.swift */,
303 | 3BB10CB4224B5ADB009BE56D /* InternalStorageProvider.swift */,
304 | );
305 | path = StorageProvider;
306 | sourceTree = "";
307 | };
308 | 3BB10CBD224B76DA009BE56D /* Wallet */ = {
309 | isa = PBXGroup;
310 | children = (
311 | 3B7FE92E2229913100CB5755 /* SLPWalletUTXO.swift */,
312 | 3B6D8277224241B000119AF0 /* SLPWalletAccount.swift */,
313 | );
314 | path = Wallet;
315 | sourceTree = "";
316 | };
317 | 3BB10CBE224B76F5009BE56D /* Token */ = {
318 | isa = PBXGroup;
319 | children = (
320 | 3B7FE92C222990FB00CB5755 /* SLPToken.swift */,
321 | 3B7FE9302229915600CB5755 /* SLPTokenUTXO.swift */,
322 | );
323 | path = Token;
324 | sourceTree = "";
325 | };
326 | 3BD7508322358D9A00CF5072 /* SLPWalletHostTests */ = {
327 | isa = PBXGroup;
328 | children = (
329 | 3BD7508422358D9A00CF5072 /* AppDelegate.swift */,
330 | 3BD7508622358D9A00CF5072 /* ViewController.swift */,
331 | 3BD7508822358D9A00CF5072 /* Main.storyboard */,
332 | 3BD7508B22358D9A00CF5072 /* Assets.xcassets */,
333 | 3BD7508D22358D9A00CF5072 /* LaunchScreen.storyboard */,
334 | 3BD7509022358D9A00CF5072 /* Info.plist */,
335 | );
336 | path = SLPWalletHostTests;
337 | sourceTree = "";
338 | };
339 | /* End PBXGroup section */
340 |
341 | /* Begin PBXHeadersBuildPhase section */
342 | 3B99B0502226251B00A1B599 /* Headers */ = {
343 | isa = PBXHeadersBuildPhase;
344 | buildActionMask = 2147483647;
345 | files = (
346 | );
347 | runOnlyForDeploymentPostprocessing = 0;
348 | };
349 | /* End PBXHeadersBuildPhase section */
350 |
351 | /* Begin PBXNativeTarget section */
352 | 3B99B0542226251B00A1B599 /* SLPWallet */ = {
353 | isa = PBXNativeTarget;
354 | buildConfigurationList = 3B99B0692226251B00A1B599 /* Build configuration list for PBXNativeTarget "SLPWallet" */;
355 | buildPhases = (
356 | 1378A790A23B5C82273005D5 /* [CP] Check Pods Manifest.lock */,
357 | 3B99B0502226251B00A1B599 /* Headers */,
358 | 3B99B0512226251B00A1B599 /* Sources */,
359 | 3B99B0522226251B00A1B599 /* Frameworks */,
360 | 3B99B0532226251B00A1B599 /* Resources */,
361 | );
362 | buildRules = (
363 | );
364 | dependencies = (
365 | );
366 | name = SLPWallet;
367 | productName = SLPWallet;
368 | productReference = 3B99B0552226251B00A1B599 /* SLPWallet.framework */;
369 | productType = "com.apple.product-type.framework";
370 | };
371 | 3B99B05D2226251B00A1B599 /* SLPWalletTests */ = {
372 | isa = PBXNativeTarget;
373 | buildConfigurationList = 3B99B06C2226251B00A1B599 /* Build configuration list for PBXNativeTarget "SLPWalletTests" */;
374 | buildPhases = (
375 | 5D80F066CE9F49464D55CC8D /* [CP] Check Pods Manifest.lock */,
376 | 3B99B05A2226251B00A1B599 /* Sources */,
377 | 3B99B05B2226251B00A1B599 /* Frameworks */,
378 | 3B99B05C2226251B00A1B599 /* Resources */,
379 | 25CE8F2B85109CF3A9716934 /* [CP] Embed Pods Frameworks */,
380 | );
381 | buildRules = (
382 | );
383 | dependencies = (
384 | 3B99B0612226251B00A1B599 /* PBXTargetDependency */,
385 | 3BD7509522358DA400CF5072 /* PBXTargetDependency */,
386 | );
387 | name = SLPWalletTests;
388 | productName = SLPWalletTests;
389 | productReference = 3B99B05E2226251B00A1B599 /* SLPWalletTests.xctest */;
390 | productType = "com.apple.product-type.bundle.unit-test";
391 | };
392 | 3BD7508122358D9A00CF5072 /* SLPWalletHostTests */ = {
393 | isa = PBXNativeTarget;
394 | buildConfigurationList = 3BD7509122358D9A00CF5072 /* Build configuration list for PBXNativeTarget "SLPWalletHostTests" */;
395 | buildPhases = (
396 | 3BD7507E22358D9A00CF5072 /* Sources */,
397 | 3BD7507F22358D9A00CF5072 /* Frameworks */,
398 | 3BD7508022358D9A00CF5072 /* Resources */,
399 | );
400 | buildRules = (
401 | );
402 | dependencies = (
403 | );
404 | name = SLPWalletHostTests;
405 | productName = SLPWalletHostTests;
406 | productReference = 3BD7508222358D9A00CF5072 /* SLPWalletHostTests.app */;
407 | productType = "com.apple.product-type.application";
408 | };
409 | /* End PBXNativeTarget section */
410 |
411 | /* Begin PBXProject section */
412 | 3B99B04C2226251B00A1B599 /* Project object */ = {
413 | isa = PBXProject;
414 | attributes = {
415 | LastSwiftUpdateCheck = 1010;
416 | LastUpgradeCheck = 1010;
417 | ORGANIZATIONNAME = Bitcoin.com;
418 | TargetAttributes = {
419 | 3B99B0542226251B00A1B599 = {
420 | CreatedOnToolsVersion = 10.1;
421 | LastSwiftMigration = 1010;
422 | };
423 | 3B99B05D2226251B00A1B599 = {
424 | CreatedOnToolsVersion = 10.1;
425 | TestTargetID = 3BD7508122358D9A00CF5072;
426 | };
427 | 3BD7508122358D9A00CF5072 = {
428 | CreatedOnToolsVersion = 10.1;
429 | };
430 | };
431 | };
432 | buildConfigurationList = 3B99B04F2226251B00A1B599 /* Build configuration list for PBXProject "SLPWallet" */;
433 | compatibilityVersion = "Xcode 9.3";
434 | developmentRegion = en;
435 | hasScannedForEncodings = 0;
436 | knownRegions = (
437 | en,
438 | Base,
439 | );
440 | mainGroup = 3B99B04B2226251B00A1B599;
441 | productRefGroup = 3B99B0562226251B00A1B599 /* Products */;
442 | projectDirPath = "";
443 | projectRoot = "";
444 | targets = (
445 | 3B99B0542226251B00A1B599 /* SLPWallet */,
446 | 3B99B05D2226251B00A1B599 /* SLPWalletTests */,
447 | 3BD7508122358D9A00CF5072 /* SLPWalletHostTests */,
448 | );
449 | };
450 | /* End PBXProject section */
451 |
452 | /* Begin PBXResourcesBuildPhase section */
453 | 3B99B0532226251B00A1B599 /* Resources */ = {
454 | isa = PBXResourcesBuildPhase;
455 | buildActionMask = 2147483647;
456 | files = (
457 | 3B7FE92B2229892F00CB5755 /* README.md in Resources */,
458 | 3BE242AC22266165003432CE /* Podfile in Resources */,
459 | 3B7FE90722283C2B00CB5755 /* SLPWallet.podspec in Resources */,
460 | );
461 | runOnlyForDeploymentPostprocessing = 0;
462 | };
463 | 3B99B05C2226251B00A1B599 /* Resources */ = {
464 | isa = PBXResourcesBuildPhase;
465 | buildActionMask = 2147483647;
466 | files = (
467 | 3BD622692248B05900503956 /* tx_details_send_tst.json in Resources */,
468 | 3BD6226722489C6100503956 /* tx_details_genesis_tst.json in Resources */,
469 | 3BD6226B2248B5F500503956 /* tx_details_mint_lvl001.json in Resources */,
470 | );
471 | runOnlyForDeploymentPostprocessing = 0;
472 | };
473 | 3BD7508022358D9A00CF5072 /* Resources */ = {
474 | isa = PBXResourcesBuildPhase;
475 | buildActionMask = 2147483647;
476 | files = (
477 | 3BD7508F22358D9A00CF5072 /* LaunchScreen.storyboard in Resources */,
478 | 3BD7508C22358D9A00CF5072 /* Assets.xcassets in Resources */,
479 | 3BD7508A22358D9A00CF5072 /* Main.storyboard in Resources */,
480 | );
481 | runOnlyForDeploymentPostprocessing = 0;
482 | };
483 | /* End PBXResourcesBuildPhase section */
484 |
485 | /* Begin PBXShellScriptBuildPhase section */
486 | 1378A790A23B5C82273005D5 /* [CP] Check Pods Manifest.lock */ = {
487 | isa = PBXShellScriptBuildPhase;
488 | buildActionMask = 2147483647;
489 | files = (
490 | );
491 | inputFileListPaths = (
492 | );
493 | inputPaths = (
494 | "${PODS_PODFILE_DIR_PATH}/Podfile.lock",
495 | "${PODS_ROOT}/Manifest.lock",
496 | );
497 | name = "[CP] Check Pods Manifest.lock";
498 | outputFileListPaths = (
499 | );
500 | outputPaths = (
501 | "$(DERIVED_FILE_DIR)/Pods-All-SLPWallet-checkManifestLockResult.txt",
502 | );
503 | runOnlyForDeploymentPostprocessing = 0;
504 | shellPath = /bin/sh;
505 | shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n";
506 | showEnvVarsInLog = 0;
507 | };
508 | 25CE8F2B85109CF3A9716934 /* [CP] Embed Pods Frameworks */ = {
509 | isa = PBXShellScriptBuildPhase;
510 | buildActionMask = 2147483647;
511 | files = (
512 | );
513 | inputFileListPaths = (
514 | "${PODS_ROOT}/Target Support Files/Pods-SLPWalletTests/Pods-SLPWalletTests-frameworks-${CONFIGURATION}-input-files.xcfilelist",
515 | );
516 | name = "[CP] Embed Pods Frameworks";
517 | outputFileListPaths = (
518 | "${PODS_ROOT}/Target Support Files/Pods-SLPWalletTests/Pods-SLPWalletTests-frameworks-${CONFIGURATION}-output-files.xcfilelist",
519 | );
520 | runOnlyForDeploymentPostprocessing = 0;
521 | shellPath = /bin/sh;
522 | shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-SLPWalletTests/Pods-SLPWalletTests-frameworks.sh\"\n";
523 | showEnvVarsInLog = 0;
524 | };
525 | 5D80F066CE9F49464D55CC8D /* [CP] Check Pods Manifest.lock */ = {
526 | isa = PBXShellScriptBuildPhase;
527 | buildActionMask = 2147483647;
528 | files = (
529 | );
530 | inputFileListPaths = (
531 | );
532 | inputPaths = (
533 | "${PODS_PODFILE_DIR_PATH}/Podfile.lock",
534 | "${PODS_ROOT}/Manifest.lock",
535 | );
536 | name = "[CP] Check Pods Manifest.lock";
537 | outputFileListPaths = (
538 | );
539 | outputPaths = (
540 | "$(DERIVED_FILE_DIR)/Pods-SLPWalletTests-checkManifestLockResult.txt",
541 | );
542 | runOnlyForDeploymentPostprocessing = 0;
543 | shellPath = /bin/sh;
544 | shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n";
545 | showEnvVarsInLog = 0;
546 | };
547 | /* End PBXShellScriptBuildPhase section */
548 |
549 | /* Begin PBXSourcesBuildPhase section */
550 | 3B99B0512226251B00A1B599 /* Sources */ = {
551 | isa = PBXSourcesBuildPhase;
552 | buildActionMask = 2147483647;
553 | files = (
554 | 3BC2880B222D022F00EB3375 /* SLPTransactionBuilder.swift in Sources */,
555 | 3B99B07E2226290100A1B599 /* UserDefaults+Extensions.swift in Sources */,
556 | 3B99B08B22262DC700A1B599 /* SLPWallet.swift in Sources */,
557 | 3B7FE92F2229913100CB5755 /* SLPWalletUTXO.swift in Sources */,
558 | 3BB10CB3224B5AB8009BE56D /* StorageProvider.swift in Sources */,
559 | 3BD750AF2237E93400CF5072 /* SecureStorageProvider.swift in Sources */,
560 | 3B99B0812226290100A1B599 /* RestNetwork.swift in Sources */,
561 | 3B288C49222C387700C1AC81 /* TokenQtyConverter.swift in Sources */,
562 | 3BB10CB5224B5ADB009BE56D /* InternalStorageProvider.swift in Sources */,
563 | 3B6D8278224241B000119AF0 /* SLPWalletAccount.swift in Sources */,
564 | 3BB10CB1224B59E6009BE56D /* SLPWalletConfig.swift in Sources */,
565 | 3B7FE9002227BF8300CB5755 /* String+Extensions.swift in Sources */,
566 | 3B7FE9312229915600CB5755 /* SLPTokenUTXO.swift in Sources */,
567 | 3B79D223223B160100FF3AFB /* Double+Extensions.swift in Sources */,
568 | 3B99B0802226290100A1B599 /* RestService.swift in Sources */,
569 | 3B7FE8D52226A67E00CB5755 /* Data+Extensions.swift in Sources */,
570 | 3B7FE933222A895E00CB5755 /* Array+Extensions.swift in Sources */,
571 | 3B7FE9292229225200CB5755 /* SLPTransactionParser.swift in Sources */,
572 | 3B7FE92D222990FB00CB5755 /* SLPToken.swift in Sources */,
573 | );
574 | runOnlyForDeploymentPostprocessing = 0;
575 | };
576 | 3B99B05A2226251B00A1B599 /* Sources */ = {
577 | isa = PBXSourcesBuildPhase;
578 | buildActionMask = 2147483647;
579 | files = (
580 | 3BB10CBA224B6CC7009BE56D /* InternalStorageProviderTest.swift in Sources */,
581 | 3B7FE8FE2227ABD900CB5755 /* SLPWalletTest.swift in Sources */,
582 | 3BD7509F2237BF9000CF5072 /* String+ExtensionsTest.swift in Sources */,
583 | 3BD750A32237C3E800CF5072 /* UserDefault+ExtensionsTest.swift in Sources */,
584 | 3BD750992237B0E400CF5072 /* TokenQtyConverterTest.swift in Sources */,
585 | 3BD7509D2237BEAB00CF5072 /* Double+ExtensionsTest.swift in Sources */,
586 | 3BD7509B2237B5AF00CF5072 /* SLPWalletUTXOTest.swift in Sources */,
587 | 3BB10CBC224B6CE7009BE56D /* SecureStorageProviderTest.swift in Sources */,
588 | 3BB10CB8224B6AE2009BE56D /* SLPWalletConfigTest.swift in Sources */,
589 | 3BD750A52237C72900CF5072 /* SLPTransactionBuilderTest.swift in Sources */,
590 | 3BD750A12237C06D00CF5072 /* SLPTokenTest.swift in Sources */,
591 | 3BD622602248943300503956 /* SLPTransactionParserTest.swift in Sources */,
592 | 3BDFC1572233A91E00DC167E /* SLPWalletTests.entitlements in Sources */,
593 | 3BE242AE222667F2003432CE /* RestServiceTest.swift in Sources */,
594 | );
595 | runOnlyForDeploymentPostprocessing = 0;
596 | };
597 | 3BD7507E22358D9A00CF5072 /* Sources */ = {
598 | isa = PBXSourcesBuildPhase;
599 | buildActionMask = 2147483647;
600 | files = (
601 | 3BD7508722358D9A00CF5072 /* ViewController.swift in Sources */,
602 | 3BD7508522358D9A00CF5072 /* AppDelegate.swift in Sources */,
603 | );
604 | runOnlyForDeploymentPostprocessing = 0;
605 | };
606 | /* End PBXSourcesBuildPhase section */
607 |
608 | /* Begin PBXTargetDependency section */
609 | 3B99B0612226251B00A1B599 /* PBXTargetDependency */ = {
610 | isa = PBXTargetDependency;
611 | target = 3B99B0542226251B00A1B599 /* SLPWallet */;
612 | targetProxy = 3B99B0602226251B00A1B599 /* PBXContainerItemProxy */;
613 | };
614 | 3BD7509522358DA400CF5072 /* PBXTargetDependency */ = {
615 | isa = PBXTargetDependency;
616 | target = 3BD7508122358D9A00CF5072 /* SLPWalletHostTests */;
617 | targetProxy = 3BD7509422358DA400CF5072 /* PBXContainerItemProxy */;
618 | };
619 | /* End PBXTargetDependency section */
620 |
621 | /* Begin PBXVariantGroup section */
622 | 3BD7508822358D9A00CF5072 /* Main.storyboard */ = {
623 | isa = PBXVariantGroup;
624 | children = (
625 | 3BD7508922358D9A00CF5072 /* Base */,
626 | );
627 | name = Main.storyboard;
628 | sourceTree = "";
629 | };
630 | 3BD7508D22358D9A00CF5072 /* LaunchScreen.storyboard */ = {
631 | isa = PBXVariantGroup;
632 | children = (
633 | 3BD7508E22358D9A00CF5072 /* Base */,
634 | );
635 | name = LaunchScreen.storyboard;
636 | sourceTree = "";
637 | };
638 | /* End PBXVariantGroup section */
639 |
640 | /* Begin XCBuildConfiguration section */
641 | 3B99B0672226251B00A1B599 /* Debug */ = {
642 | isa = XCBuildConfiguration;
643 | buildSettings = {
644 | ALWAYS_SEARCH_USER_PATHS = NO;
645 | CLANG_ANALYZER_NONNULL = YES;
646 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
647 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++14";
648 | CLANG_CXX_LIBRARY = "libc++";
649 | CLANG_ENABLE_MODULES = YES;
650 | CLANG_ENABLE_OBJC_ARC = YES;
651 | CLANG_ENABLE_OBJC_WEAK = YES;
652 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
653 | CLANG_WARN_BOOL_CONVERSION = YES;
654 | CLANG_WARN_COMMA = YES;
655 | CLANG_WARN_CONSTANT_CONVERSION = YES;
656 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
657 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
658 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
659 | CLANG_WARN_EMPTY_BODY = YES;
660 | CLANG_WARN_ENUM_CONVERSION = YES;
661 | CLANG_WARN_INFINITE_RECURSION = YES;
662 | CLANG_WARN_INT_CONVERSION = YES;
663 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
664 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
665 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
666 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
667 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
668 | CLANG_WARN_STRICT_PROTOTYPES = YES;
669 | CLANG_WARN_SUSPICIOUS_MOVE = YES;
670 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
671 | CLANG_WARN_UNREACHABLE_CODE = YES;
672 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
673 | CODE_SIGN_IDENTITY = "iPhone Developer";
674 | COPY_PHASE_STRIP = NO;
675 | CURRENT_PROJECT_VERSION = 1;
676 | DEBUG_INFORMATION_FORMAT = dwarf;
677 | ENABLE_STRICT_OBJC_MSGSEND = YES;
678 | ENABLE_TESTABILITY = YES;
679 | GCC_C_LANGUAGE_STANDARD = gnu11;
680 | GCC_DYNAMIC_NO_PIC = NO;
681 | GCC_NO_COMMON_BLOCKS = YES;
682 | GCC_OPTIMIZATION_LEVEL = 0;
683 | GCC_PREPROCESSOR_DEFINITIONS = (
684 | "DEBUG=1",
685 | "$(inherited)",
686 | );
687 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
688 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
689 | GCC_WARN_UNDECLARED_SELECTOR = YES;
690 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
691 | GCC_WARN_UNUSED_FUNCTION = YES;
692 | GCC_WARN_UNUSED_VARIABLE = YES;
693 | IPHONEOS_DEPLOYMENT_TARGET = 12.1;
694 | MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
695 | MTL_FAST_MATH = YES;
696 | ONLY_ACTIVE_ARCH = YES;
697 | SDKROOT = iphoneos;
698 | SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG;
699 | SWIFT_OPTIMIZATION_LEVEL = "-Onone";
700 | SWIFT_VERSION = 4.0;
701 | VERSIONING_SYSTEM = "apple-generic";
702 | VERSION_INFO_PREFIX = "";
703 | };
704 | name = Debug;
705 | };
706 | 3B99B0682226251B00A1B599 /* Release */ = {
707 | isa = XCBuildConfiguration;
708 | buildSettings = {
709 | ALWAYS_SEARCH_USER_PATHS = NO;
710 | CLANG_ANALYZER_NONNULL = YES;
711 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
712 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++14";
713 | CLANG_CXX_LIBRARY = "libc++";
714 | CLANG_ENABLE_MODULES = YES;
715 | CLANG_ENABLE_OBJC_ARC = YES;
716 | CLANG_ENABLE_OBJC_WEAK = YES;
717 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
718 | CLANG_WARN_BOOL_CONVERSION = YES;
719 | CLANG_WARN_COMMA = YES;
720 | CLANG_WARN_CONSTANT_CONVERSION = YES;
721 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
722 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
723 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
724 | CLANG_WARN_EMPTY_BODY = YES;
725 | CLANG_WARN_ENUM_CONVERSION = YES;
726 | CLANG_WARN_INFINITE_RECURSION = YES;
727 | CLANG_WARN_INT_CONVERSION = YES;
728 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
729 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
730 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
731 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
732 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
733 | CLANG_WARN_STRICT_PROTOTYPES = YES;
734 | CLANG_WARN_SUSPICIOUS_MOVE = YES;
735 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
736 | CLANG_WARN_UNREACHABLE_CODE = YES;
737 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
738 | CODE_SIGN_IDENTITY = "iPhone Developer";
739 | COPY_PHASE_STRIP = NO;
740 | CURRENT_PROJECT_VERSION = 1;
741 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
742 | ENABLE_NS_ASSERTIONS = NO;
743 | ENABLE_STRICT_OBJC_MSGSEND = YES;
744 | GCC_C_LANGUAGE_STANDARD = gnu11;
745 | GCC_NO_COMMON_BLOCKS = YES;
746 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
747 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
748 | GCC_WARN_UNDECLARED_SELECTOR = YES;
749 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
750 | GCC_WARN_UNUSED_FUNCTION = YES;
751 | GCC_WARN_UNUSED_VARIABLE = YES;
752 | IPHONEOS_DEPLOYMENT_TARGET = 12.1;
753 | MTL_ENABLE_DEBUG_INFO = NO;
754 | MTL_FAST_MATH = YES;
755 | SDKROOT = iphoneos;
756 | SWIFT_COMPILATION_MODE = wholemodule;
757 | SWIFT_OPTIMIZATION_LEVEL = "-O";
758 | SWIFT_VERSION = 4.0;
759 | VALIDATE_PRODUCT = YES;
760 | VERSIONING_SYSTEM = "apple-generic";
761 | VERSION_INFO_PREFIX = "";
762 | };
763 | name = Release;
764 | };
765 | 3B99B06A2226251B00A1B599 /* Debug */ = {
766 | isa = XCBuildConfiguration;
767 | baseConfigurationReference = 890110000F200FAF616C6E2E /* Pods-All-SLPWallet.debug.xcconfig */;
768 | buildSettings = {
769 | CLANG_ENABLE_MODULES = YES;
770 | CODE_SIGN_IDENTITY = "";
771 | CODE_SIGN_STYLE = Automatic;
772 | DEFINES_MODULE = YES;
773 | DEVELOPMENT_TEAM = 299HJ3G3BP;
774 | DYLIB_COMPATIBILITY_VERSION = 1;
775 | DYLIB_CURRENT_VERSION = 1;
776 | DYLIB_INSTALL_NAME_BASE = "@rpath";
777 | INFOPLIST_FILE = SLPWallet/Info.plist;
778 | INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks";
779 | LD_RUNPATH_SEARCH_PATHS = (
780 | "$(inherited)",
781 | "@executable_path/Frameworks",
782 | "@loader_path/Frameworks",
783 | );
784 | LIBRARY_SEARCH_PATHS = (
785 | "$(inherited)",
786 | "$(PROJECT_DIR)/Sample/SLPWalletTestApp/Pods/BitcoinKit/Libraries/secp256k1/lib",
787 | "$(PROJECT_DIR)/Sample/SLPWalletTestApp/Pods/BitcoinKit/Libraries/openssl/lib",
788 | );
789 | PRODUCT_BUNDLE_IDENTIFIER = com.bitcoin.SLPWallet;
790 | PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)";
791 | SKIP_INSTALL = YES;
792 | SWIFT_OPTIMIZATION_LEVEL = "-Onone";
793 | SWIFT_VERSION = 4.0;
794 | TARGETED_DEVICE_FAMILY = "1,2";
795 | };
796 | name = Debug;
797 | };
798 | 3B99B06B2226251B00A1B599 /* Release */ = {
799 | isa = XCBuildConfiguration;
800 | baseConfigurationReference = FB3A348EC237FAE26363CAC1 /* Pods-All-SLPWallet.release.xcconfig */;
801 | buildSettings = {
802 | CLANG_ENABLE_MODULES = YES;
803 | CODE_SIGN_IDENTITY = "";
804 | CODE_SIGN_STYLE = Automatic;
805 | DEFINES_MODULE = YES;
806 | DEVELOPMENT_TEAM = 299HJ3G3BP;
807 | DYLIB_COMPATIBILITY_VERSION = 1;
808 | DYLIB_CURRENT_VERSION = 1;
809 | DYLIB_INSTALL_NAME_BASE = "@rpath";
810 | INFOPLIST_FILE = SLPWallet/Info.plist;
811 | INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks";
812 | LD_RUNPATH_SEARCH_PATHS = (
813 | "$(inherited)",
814 | "@executable_path/Frameworks",
815 | "@loader_path/Frameworks",
816 | );
817 | LIBRARY_SEARCH_PATHS = (
818 | "$(inherited)",
819 | "$(PROJECT_DIR)/Sample/SLPWalletTestApp/Pods/BitcoinKit/Libraries/secp256k1/lib",
820 | "$(PROJECT_DIR)/Sample/SLPWalletTestApp/Pods/BitcoinKit/Libraries/openssl/lib",
821 | );
822 | PRODUCT_BUNDLE_IDENTIFIER = com.bitcoin.SLPWallet;
823 | PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)";
824 | SKIP_INSTALL = YES;
825 | SWIFT_VERSION = 4.0;
826 | TARGETED_DEVICE_FAMILY = "1,2";
827 | };
828 | name = Release;
829 | };
830 | 3B99B06D2226251B00A1B599 /* Debug */ = {
831 | isa = XCBuildConfiguration;
832 | baseConfigurationReference = F1DBD6941A749C2400954E1F /* Pods-SLPWalletTests.debug.xcconfig */;
833 | buildSettings = {
834 | BUNDLE_LOADER = "$(TEST_HOST)";
835 | CODE_SIGN_ENTITLEMENTS = "${SRCROOT}/SLPWalletTests/SLPWalletTests.entitlements";
836 | CODE_SIGN_STYLE = Automatic;
837 | DEVELOPMENT_TEAM = 299HJ3G3BP;
838 | INFOPLIST_FILE = SLPWalletTests/Info.plist;
839 | LD_RUNPATH_SEARCH_PATHS = (
840 | "$(inherited)",
841 | "@executable_path/Frameworks",
842 | "@loader_path/Frameworks",
843 | );
844 | PRODUCT_BUNDLE_IDENTIFIER = com.bitcoin.SLPWalletTests;
845 | PRODUCT_NAME = "$(TARGET_NAME)";
846 | SWIFT_VERSION = 4.0;
847 | TARGETED_DEVICE_FAMILY = "1,2";
848 | TEST_HOST = "$(BUILT_PRODUCTS_DIR)/SLPWalletHostTests.app/SLPWalletHostTests";
849 | };
850 | name = Debug;
851 | };
852 | 3B99B06E2226251B00A1B599 /* Release */ = {
853 | isa = XCBuildConfiguration;
854 | baseConfigurationReference = 1DFFAD2AFD166179BF17DA9E /* Pods-SLPWalletTests.release.xcconfig */;
855 | buildSettings = {
856 | BUNDLE_LOADER = "$(TEST_HOST)";
857 | CODE_SIGN_ENTITLEMENTS = "${SRCROOT}/SLPWalletTests/SLPWalletTests.entitlements";
858 | CODE_SIGN_STYLE = Automatic;
859 | DEVELOPMENT_TEAM = 299HJ3G3BP;
860 | INFOPLIST_FILE = SLPWalletTests/Info.plist;
861 | LD_RUNPATH_SEARCH_PATHS = (
862 | "$(inherited)",
863 | "@executable_path/Frameworks",
864 | "@loader_path/Frameworks",
865 | );
866 | PRODUCT_BUNDLE_IDENTIFIER = com.bitcoin.SLPWalletTests;
867 | PRODUCT_NAME = "$(TARGET_NAME)";
868 | SWIFT_VERSION = 4.0;
869 | TARGETED_DEVICE_FAMILY = "1,2";
870 | TEST_HOST = "$(BUILT_PRODUCTS_DIR)/SLPWalletHostTests.app/SLPWalletHostTests";
871 | };
872 | name = Release;
873 | };
874 | 3BD7509222358D9A00CF5072 /* Debug */ = {
875 | isa = XCBuildConfiguration;
876 | buildSettings = {
877 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
878 | CODE_SIGN_STYLE = Automatic;
879 | DEVELOPMENT_TEAM = 299HJ3G3BP;
880 | INFOPLIST_FILE = SLPWalletHostTests/Info.plist;
881 | LD_RUNPATH_SEARCH_PATHS = (
882 | "$(inherited)",
883 | "@executable_path/Frameworks",
884 | );
885 | PRODUCT_BUNDLE_IDENTIFIER = com.bitcoin.SLPWalletHostTests;
886 | PRODUCT_NAME = "$(TARGET_NAME)";
887 | SWIFT_VERSION = 4.2;
888 | TARGETED_DEVICE_FAMILY = "1,2";
889 | };
890 | name = Debug;
891 | };
892 | 3BD7509322358D9A00CF5072 /* Release */ = {
893 | isa = XCBuildConfiguration;
894 | buildSettings = {
895 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
896 | CODE_SIGN_STYLE = Automatic;
897 | DEVELOPMENT_TEAM = 299HJ3G3BP;
898 | INFOPLIST_FILE = SLPWalletHostTests/Info.plist;
899 | LD_RUNPATH_SEARCH_PATHS = (
900 | "$(inherited)",
901 | "@executable_path/Frameworks",
902 | );
903 | PRODUCT_BUNDLE_IDENTIFIER = com.bitcoin.SLPWalletHostTests;
904 | PRODUCT_NAME = "$(TARGET_NAME)";
905 | SWIFT_VERSION = 4.2;
906 | TARGETED_DEVICE_FAMILY = "1,2";
907 | };
908 | name = Release;
909 | };
910 | /* End XCBuildConfiguration section */
911 |
912 | /* Begin XCConfigurationList section */
913 | 3B99B04F2226251B00A1B599 /* Build configuration list for PBXProject "SLPWallet" */ = {
914 | isa = XCConfigurationList;
915 | buildConfigurations = (
916 | 3B99B0672226251B00A1B599 /* Debug */,
917 | 3B99B0682226251B00A1B599 /* Release */,
918 | );
919 | defaultConfigurationIsVisible = 0;
920 | defaultConfigurationName = Release;
921 | };
922 | 3B99B0692226251B00A1B599 /* Build configuration list for PBXNativeTarget "SLPWallet" */ = {
923 | isa = XCConfigurationList;
924 | buildConfigurations = (
925 | 3B99B06A2226251B00A1B599 /* Debug */,
926 | 3B99B06B2226251B00A1B599 /* Release */,
927 | );
928 | defaultConfigurationIsVisible = 0;
929 | defaultConfigurationName = Release;
930 | };
931 | 3B99B06C2226251B00A1B599 /* Build configuration list for PBXNativeTarget "SLPWalletTests" */ = {
932 | isa = XCConfigurationList;
933 | buildConfigurations = (
934 | 3B99B06D2226251B00A1B599 /* Debug */,
935 | 3B99B06E2226251B00A1B599 /* Release */,
936 | );
937 | defaultConfigurationIsVisible = 0;
938 | defaultConfigurationName = Release;
939 | };
940 | 3BD7509122358D9A00CF5072 /* Build configuration list for PBXNativeTarget "SLPWalletHostTests" */ = {
941 | isa = XCConfigurationList;
942 | buildConfigurations = (
943 | 3BD7509222358D9A00CF5072 /* Debug */,
944 | 3BD7509322358D9A00CF5072 /* Release */,
945 | );
946 | defaultConfigurationIsVisible = 0;
947 | defaultConfigurationName = Release;
948 | };
949 | /* End XCConfigurationList section */
950 | };
951 | rootObject = 3B99B04C2226251B00A1B599 /* Project object */;
952 | }
953 |
--------------------------------------------------------------------------------