An Ethereum Plugin for the Pocket iOS SDK that conforms to the PocketPlugin
interface. Uses web3.swift
and Cryptoswift
for core cryptography and Ethereum related functions. Conforms to the Pocket API guidelines.
Need to install the following pod in your Podfile:
pod 'Pocket', :git => 'https://github.com/pokt-network/pocket-ios-sdk.git', :branch => 'master'
pod 'PocketEth', :git => 'https://github.com/pokt-network/pocket-ios-eth.git', :branch => 'master'
pod 'SwiftKeychainWrapper', :git => 'https://github.com/jrendel/SwiftKeychainWrapper.git', :branch => 'develop', :commit => '77f73c354d695d976bcf1437fc9fbcea981aa2b4'
Pocket Network offers a node running in Rinkeby for testing.
https://ethereum.pokt.network
To easily set it up:
1- In your appDelegate
import Pocket
and PocketEth
2- Add Configuration
protocol to the class:
class AppDelegate: UIResponder, UIApplicationDelegate, Configuration, {
3- Implement nodeURL
with the node url:
var nodeURL: URL { get { return URL.init(string: "https://ethereum.pokt.network")! } }
4- After we specified the URL, we need to set the configuration for Pocket in the Application
function: Pocket.shared.setConfiguration(config: self)
Currently the Pocket team is offering the https://ethereum.pokt.network
endpoint, which supports both Mainnet and the Rinkeby testnet with the following subnetwork
identifiers:
1
for Mainnet
4
for Rinkeby
The wallet creation primarily uses the web3 library and the SECP256k1.generatePrivateKey
function and saves to the keystore on the device. Developers do not have to worry about encrypting, storing or retrieving the wallet from the device.
Follow the following example to create an Ethereum Wallet:
let newWallet = try PocketEth.createWallet(subnetwork: "4", data: nil)
And to import, you need to define:
1- Public Key
2- Private Key
3- Subnetwork
4- Data
let privateKey = "0x";
let address = "0x";
let subnetwork = "32";
let importWallet = try PocketEth.importWallet(privateKey: privateKey, subnetwork: "4", address: address, data: nil)
public static func createTransaction(wallet: Wallet, params: [AnyHashable : Any]) throws -> Transaction
To create an Ethereum transaction you need the following parameters:
1- nonce
: A counter that increments by +1 each time a transaction is created on an account. You can retrieve the current transaction count using eth_getTransactionCount
(Query)
2- gasPrice
: The price of the transaction denominated in wei
3- gasLimit
: Max amount of gas to be used for transaction denominated in wei
4- to
: Public address receiving the transaction
5- value
(optional): Amount of ETH being sent in the transaction
6- data
(optional): Data such as ABI of the function being called on a smart contract can be sent through the data field
By passing these in through the params
dictionary the Ethereum plugin abstracts all the difficulty of creating transactions for the developer by returning a simple Transaction
object. An example transaction in creating a Quest in BANANO Quest:
let functionABI = "{\"constant\":false,\"inputs\":[{\"name\":\"_tokenAddress\",\"type\":\"address\"},{\"name\":\"_name\",\"type\":\"string\"},{\"name\":\"_hint\",\"type\":\"string\"},{\"name\":\"_maxWinners\",\"type\":\"uint256\"},{\"name\":\"_merkleRoot\",\"type\":\"bytes32\"},{\"name\":\"_merkleBody\",\"type\":\"string\"},{\"name\":\"_metadata\",\"type\":\"string\"}],\"name\":\"createQuest\",\"outputs\":[],\"payable\":true,\"stateMutability\":\"payable\",\"type\":\"function\"}"
var functionParameters = [AnyObject]()
functionParameters.append(tokenAddress as AnyObject)
functionParameters.append(questName.description as AnyObject)
functionParameters.append(hint.description as AnyObject)
functionParameters.append(maxWinners as AnyObject)
functionParameters.append(merkleRoot as AnyObject)
functionParameters.append(merkleBody as AnyObject)
functionParameters.append(metadata as AnyObject)
let txParams = [
"from": wallet.address,
"nonce": BigUInt.init(transactionCount),
"to": tavernAddress,
"value": BigUInt.init(ethPrizeWei),
"chainID": AppConfiguration.chainID,
"gasLimit": BigUInt.init(2000000),
"gasPrice": BigUInt.init(1000000000),
"data": [
"abi": functionABI,
"params": functionParameters
] as [AnyHashable: Any]
] as [AnyHashable: Any]
guard let transaction = try? PocketEth.createTransaction(wallet: wallet, params: txParams) else {
self.error = PocketPluginError.transactionCreationError("Error creating transaction")
self.finish()
return
}
Pocket.shared.sendTransaction(transaction: transaction) { (transactionResponse, error) in
if error != nil {
self.error = error
self.finish()
return
}
guard let txHash = transactionResponse?.hash else {
self.error = UploadQuestOperationError.invalidTxHash
self.finish()
return
}
self.txHash = txHash
self.finish()
public static func createQuery(subnetwork: String, params: [AnyHashable: Any], decoder: [AnyHashable: Any]?) throws -> Query
To create a Pocket Query for Ethereum you'll need the subnetwork
and params
for the specific JSON RPC call you are wishing to make. There are two types of params to create a Query:
rpcMethod
: Name of the smart contract method you are callingrpcParams
: Inputs of the smart contract method you are calling
The decoder
dictionary allows the developer to specify the return types from the read request.
An example in creating a getBalance
Query and getting the balance of an account using Pocket:
let params = [
"rpcMethod": "eth_getBalance",
"rpcParams": [address, "latest"]
] as [AnyHashable: Any]
guard let query = try? PocketEth.createQuery(params: params, decoder: nil) else {
self.error = PocketPluginError.queryCreationError("Error creating query")
self.finish()
return
}
Pocket.shared.executeQuery(query: query) { (queryResponse, error) in
if error != nil {
self.error = error
self.finish()
return
}
Creating a Query for a smart contract constant is a little bit more involved, as you need to provide the ABI interface for the method you are calling, the functionParameters
and the decoder
. An example in getting a list of Quests from BANANO Quest:
var tx = [AnyHashable: Any]()
let functionABI = "{\"constant\":true,\"inputs\":[{\"name\":\"_tokenAddress\",\"type\":\"address\"},{\"name\":\"_questIndex\",\"type\":\"uint256\"}],\"name\":\"getQuest\",\"outputs\":[{\"name\":\"\",\"type\":\"address\"},{\"name\":\"\",\"type\":\"uint256\"},{\"name\":\"\",\"type\":\"string\"},{\"name\":\"\",\"type\":\"string\"},{\"name\":\"\",\"type\":\"bytes32\"},{\"name\":\"\",\"type\":\"string\"},{\"name\":\"\",\"type\":\"uint256\"},{\"name\":\"\",\"type\":\"string\"},{\"name\":\"\",\"type\":\"bool\"},{\"name\":\"\",\"type\":\"uint256\"},{\"name\":\"\",\"type\":\"uint256\"}],\"payable\":false,\"stateMutability\":\"view\",\"type\":\"function\"}"
let functionParameters = [tokenAddress, questIndex] as [AnyObject]
guard let data = try? PocketEth.encodeFunction(functionABI: functionABI, parameters: functionParameters).toHexString() else {
self.error = PocketPluginError.queryCreationError("Error creating query")
self.finish()
return
}
tx["to"] = tavernAddress
tx["data"] = "0x" + data
tx["from"] = self.playerAddress
let params = [
"rpcMethod": "eth_call",
"rpcParams": [tx, "latest"]
] as [AnyHashable: Any]
let decoder = [
"returnTypes": ["address", "uint256", "string", "string", "bytes32", "string", "uint256", "string", "bool", "uint256", "uint256"]
] as [AnyHashable : Any]
guard let query = try? PocketEth.createQuery(params: params, decoder: decoder) else {
self.error = PocketPluginError.queryCreationError("Error creating query")
self.finish()
return
}
Pocket.shared.executeQuery(query: query) { (queryResponse, error) in
if error != nil {
self.error = error
self.finish()
return
}
guard let questArr = queryResponse?.result?.value() as? [JSON] else {
self.error = DownloadQuestOperationError.questParsing
self.finish()
return
}
let creator = questArr[0].value() as? String ?? ""
let index = questArr[1].value() as? String ?? "0"
let name = questArr[2].value() as? String ?? ""
let hint = questArr[3].value() as? String ?? ""
let merkleRoot = questArr[4].value() as? String ?? ""
let merkleBody = questArr[5].value() as? String ?? ""
let maxWinners = questArr[6].value() as? String ?? "0"
let metadata = questArr[7].value() as? String ?? ""
let valid = questArr[8].value() as? Bool ?? false
let winnersAmount = questArr[9].value() as? String ?? "0"
let claimersAmount = questArr[10].value() as? String ?? "0"
self.questDict = [
"creator": creator,
"index": index,
"name": name,
"hint": hint,
"merkleRoot": merkleRoot,
"merkleBody": merkleBody,
"maxWinners": maxWinners,
"metadata": metadata,
"valid": valid,
"winnersAmount": winnersAmount,
"claimersAmount": claimersAmount
] as [AnyHashable: Any]
self.finish()