Giter Club home page Giter Club logo

web3swift's Introduction

Installation

CocoaPods

Web3 is available through CocoaPods. To install it, simply add the following line to your Podfile:

pod 'Web3Swift.io'

Sending ethers

To send some wei from an account with a private key 0x1636e10756e62baabddd4364010444205f1216bdb1644ff8f776f6e2982aa9f5 to an account with an address 0x79d2c50Ba0cA4a2C6F8D65eBa1358bEfc1cFD403 on a mainnet:

import Web3Swift

let sender: PrivateKey = EthPrivateKey(
    hex: "0x1636e10756e62baabddd4364010444205f1216bdb1644ff8f776f6e2982aa9f5"
)

let recipient: BytesScalar = EthAddress(
    hex: "0x79d2c50Ba0cA4a2C6F8D65eBa1358bEfc1cFD403"
)

let network: Network = InfuraNetwork(
    chain: "mainnet",
    apiKey: "0c4d6dc730244b4185a6bde26f981bff"
)

let amount: BytesScalar = EthNumber(
    hex: "0xde0b6b3a7640000" // 10^18 in hex that represents 1 ETH
)

let response = try SendRawTransactionProcedure(
    network: network,
    transactionBytes: EthDirectTransactionBytes(
        network: network,
        senderKey: sender,
        recipientAddress: recipient,
        weiAmount: amount
    )
).call()

//If Ethereum network accepts the transaction, you could get transaction hash from the response. Otherwise, library will throw `DescribedError`
print(response["result"].string ?? "Something went wrong")

If you want to specify gas price or gas amount take a look at EthDirectTransactionBytes.swift.

To send ether instead of wei:

let ethAmount = 1.1

try SendRawTransactionProcedure(
    ...,
    weiAmount: EthToWei(
        amount: ethAmount
    )
).call()

Dealing with ERC-20 tokens

Sending tokens to an address

To send some ERC-20 tokens, for example OmiseGO, we need to get the smart contract address. OMG tokens are managed by smart contract at 0xd26114cd6EE289AccF82350c8d8487fedB8A0C07. In this example we send token from an account with a private key 0x1636e10756e62baabddd4364010444205f1216bdb1644ff8f776f6e2982aa9f5 to an account with an address 0x79d2c50Ba0cA4a2C6F8D65eBa1358bEfc1cFD403 on a mainnet:

import Web3Swift

let network: Network = InfuraNetwork(
    chain: "mainnet", apiKey: "0c4d6dc730244b4185a6bde26f981bff"
)

let sender: PrivateKey = EthPrivateKey(
    hex: "0x1636e10756e62baabddd4364010444205f1216bdb1644ff8f776f6e2982aa9f5"
)

let recipient: BytesScalar = EthAddress(
    hex: "0x79d2c50Ba0cA4a2C6F8D65eBa1358bEfc1cFD403"
)

let token: BytesScalar = EthAddress(
    hex: "0xd26114cd6EE289AccF82350c8d8487fedB8A0C07"
)

let amount: BytesScalar = EthNumber(
    hex: "0xde0b6b3a7640000" // 10^18 in hex that represents 1 OMG token
)

let response = try SendRawTransactionProcedure(
    network: network,
    transactionBytes: EthContractCallBytes(
        network: network,
        senderKey: sender,
        contractAddress: token,
        weiAmount: EthNumber(
            hex: "0x00" //We do not need to provide ethers to not payable functions.
        ),
        functionCall: EncodedABIFunction(
            signature: SimpleString(
                string: "transfer(address,uint256)"
            ),
            parameters: [
                ABIAddress(
                    address: recipient
                ),
                ABIUnsignedNumber(
                    origin: amount
                )
            ]
        )
    )
).call()

//If Ethereum network accepts the transaction, you could get transaction hash from the response. Otherwise, library will throw `DescribedError`
print(response["result"].string ?? "Something went wrong")

You can swiftly deal with other ERC-20 functions just by encoding another EncodedABIFunction .

Sending delegated tokens to an address

Here is an example of encoding transferFrom(from,to,value) function

EncodedABIFunction(
    signature: SimpleString(
        string: "transferFrom(address,address,uint256)"
    ),
    parameters: [
        ABIAddress(
            address: EthAddress(
                hex: "0xFBb1b73C4f0BDa4f67dcA266ce6Ef42f520fBB98"
            )
        ),
        ABIAddress(
            address: EthAddress(
                hex: "0x79d2c50Ba0cA4a2C6F8D65eBa1358bEfc1cFD403"
            )
        ),
        ABIUnsignedNumber(
            origin: EthNumber(
                hex: "0x01"
            )
        )
    ]
)

More encoding example including advanced ones are placed at Example/Tests/ABI

Checking an address balance

You do not need to send transaction for reading data from a smart contract. Here is an example of checking address balance by making a call to smart contract function balanceOf(owner).

let balance = try HexAsDecimalString(
    hex: EthContractCall(
        network: InfuraNetwork(
            chain: "mainnet", apiKey: "0c4d6dc730244b4185a6bde26f981bff"
        ),
        contractAddress: EthAddress(
            hex: "0xd26114cd6EE289AccF82350c8d8487fedB8A0C07" //OmiseGO token contract
        ),
        functionCall: EncodedABIFunction(
            signature: SimpleString(
                string: "balanceOf(address)"
            ),
            parameters: [
                ABIAddress(
                    address: EthAddress(
                        hex: "0xFBb1b73C4f0BDa4f67dcA266ce6Ef42f520fBB98" //Bittrex
                    )
                )
            ]
        )
    )
).value()

print(balance) // 13098857909137917398909558 is 13 098 857.909137917398909558 OMG tokens

Signing

import CryptoSwift

// Add your private key
let privateKey = EthPrivateKey(
        hex: "YOUR_PRIVATE_KEY"
)

// Form the bytes for your message
// In our example we sign null Ethereum address
let messageBytes = try! EthAddress(
        hex: "0x0000000000000000000000000000000000000000"
).value().bytes

// Create a message
// Don't forget that some services may expect
// a message with Ethereum prefix as here
let message = ConcatenatedBytes(
        bytes: [
            //Ethereum prefix
            UTF8StringBytes(
                    string: SimpleString(
                            string: "\u{19}Ethereum Signed Message:\n32"
                    )
            ),
            //message
            Keccak256Bytes(
                    origin: SimpleBytes(
                            bytes:
                    )
            )
        ]
)

// Use your custom hash function if needed
let hashFunction = SHA3(variant: .keccak256).calculate

// Create the signature
// Calculations are performed in a lazy way
// so you don't have to worry about performance
let signature = SECP256k1Signature(
        privateKey: privateKey,
        message: message,
        hashFunction: hashFunction
)

// Now you can retrieve all the parameters
// of the signature or use it for the signing with web3
let r = PrefixedHexString(
        bytes: try! signature.r()
)
let s = PrefixedHexString(
        bytes: try! signature.s()
)
let v = try! signature.recoverID().value() + 27

Getting information about transactions

Fetching information

Getting results of the recent or previous transaction is one of the most common tasks during developing interactions with DApps. There are two JSON-RPC methods for getting basic and additional transaction info. The first one is eth_getTransactionByHash (example) and the second one is eth_getTransactionReceipt(example). You could use next library example to get needed information from the Ethereum blockchain.

import Web3Swift
import SwiftyJSON

let transactionHash = BytesFromHexString(
    hex: "0x5798fbc45e3b63832abc4984b0f3574a13545f415dd672cd8540cd71f735db56"
)

let network = InfuraNetwork(
    chain: "mainnet",
    apiKey: "0c4d6dc730244b4185a6bde26f981bff"
)

let basicInfo: JSON = try TransactionProcedure(
    network: network,
    transactionHash: transactionHash
).call()

let advancedInfo: JSON = try TransactionReceiptProcedure(
    network: network,
    transactionHash: transactionHash
).call()

print(basicInfo["result"].dictionary ?? "Something went wrong")
/**
[
    "blockNumber": 0x196666,
    "value": 0x0,
    "v": 0x1b,
    "input":0x612e45a3000000000000000000000000b656b2a9c3b2416437a811e07466ca712f5a5b5a000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000c000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000093a80000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000116c6f6e656c792c20736f206c6f6e656c7900000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000,
    "hash": 0x5798fbc45e3b63832abc4984b0f3574a13545f415dd672cd8540cd71f735db56,
    "to": 0xbb9bc244d798123fde783fcc1c72d3bb8c189413,
    "transactionIndex": 0x7,
    "gasPrice": 0x4a817c800,
    "r": 0xd92d67e4a982c45c78c1260fc2f644ed78483e2bf7d6151aab9ea40a8e172472,
    "nonce": 0x0,
    "blockHash": 0x1f716531f40858da4d4b08269f571f9f22c7b8bd921764e8bdf9cb2e0508efa1,
    "from": 0xb656b2a9c3b2416437a811e07466ca712f5a5b5a,
    "s": 0x6ee7e259e4f13378cf167bb980659520a7e5897643a2642586f246c6de5367d6,
    "gas": 0x4c449
]
*/

print(advancedInfo["result"].dictionary ?? "Something went wrong")

/**
[
    "root": 0xee69c77c73cd53b90e928e786b1c7f5b743a36dccd877128cf1dce7b46980a97,
    "blockNumber": 0x196666,
    "transactionIndex": 0x7,
    "transactionHash": 0x5798fbc45e3b63832abc4984b0f3574a13545f415dd672cd8540cd71f735db56,
    "blockHash": 0x1f716531f40858da4d4b08269f571f9f22c7b8bd921764e8bdf9cb2e0508efa1,
    "from": 0xb656b2a9c3b2416437a811e07466ca712f5a5b5a,
    "contractAddress": null,
    "logsBloom": 0x00000000000000020000000000020000000000000000000000000000000000000000000000000000000000000000000000000000000000800000000000080000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000020000000000000000000200000000000000000000800000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000000000000,
    "to": 0xbb9bc244d798123fde783fcc1c72d3bb8c189413,
    "logs": [
        {
            "blockNumber" : "0x196666",
            "topics" : [
                "0x5790de2c279e58269b93b12828f56fd5f2bc8ad15e61ce08572585c81a38756f",
                "0x000000000000000000000000000000000000000000000000000000000000003b"
            ],
            "data" : "0x000000000000000000000000b656b2a9c3b2416437a811e07466ca712f5a5b5a00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000000000116c6f6e656c792c20736f206c6f6e656c79000000000000000000000000000000",
            "logIndex" : "0x5",
            "transactionHash" : "0x5798fbc45e3b63832abc4984b0f3574a13545f415dd672cd8540cd71f735db56",
            "removed" : false,
            "address" : "0xbb9bc244d798123fde783fcc1c72d3bb8c189413",
            "blockHash" : "0x1f716531f40858da4d4b08269f571f9f22c7b8bd921764e8bdf9cb2e0508efa1",
            "transactionIndex" : "0x7"
        }
    ],
    "gasUsed": 0x33da9,
    "cumulativeGasUsed": 0xf98e3
]
*/

NOTE: Library is still in development. Domain level objects for all RPC structures are on the roadmap.

Parsing transaction

After fetching the information you could transparently convert it to suitable objects.

// Get the number of the block in which the transaction occurred
let block = try HexAsDecimalString(
    hex: EthNumber(
        hex: basicInfo["result"]["blockNumber"].stringValue
    )   
).value()

print(block)
// 1664614

// Get the recipient of the transaction
let recipient = try EthAddress(
    hex: basicInfo["result"]["to"].stringValue
).value().toHexString()

print(recipient)
// bb9bc244d798123fde783fcc1c72d3bb8c189413

// Get the transaction fee in WEI
let gasPrice = EthNumber(
    hex: basicInfo["result"]["gasPrice"].stringValue
)

let gasUsed = EthNumber(
    hex: advancedInfo["result"]["gasUsed"].stringValue
)

let fee = try HexAsDecimalString(
    hex: gasPrice * gasUsed
).value()

print(fee)
// 4247860000000000 WEI = 0,00424786 ETH

Parsing transaction's input data

You could easily parse any transaction's input by knowing it's ABI or types the data passed into it.

// Parse the transaction input parameters
/*
    newProposal(
        [0] address _recipient,
        [1] uint256 _amount,
        [2] string _description,
        [3] bytes _transactionData,
        [4] uint256 _debatingPeriod,
        [5] bool _newCurator
    )
*/

// Prepare the transaction's input for parsing - trim the signature of the executed function
let input = ABIMessage(
    message: TrimmedPrefixString(
        string: SimpleString{
            basicInfo["result"]["input"].stringValue
        },
        prefix: SimpleString{
            "0x612e45a3"
        }
    )
)

// Get the recipient's address
let abiRecipient = try DecodedABIAddress(
    abiMessage: input,
    index: 0
).value().toHexString()

print(abiRecipient)
// b656b2a9c3b2416437a811e07466ca712f5a5b5a

// Get the description string
let description = try DecodedABIString(
    abiMessage: input,
    index: 2
).value()

print(description)
// lonely, so lonely

// Get the debating period number
let debatingPeriod = try HexAsDecimalString(
    hex: DecodedABINumber(
        abiMessage: input,
        index: 4
    )
).value()

print(debatingPeriod)
// 604800

// Get the boolean flag
let flag = try DecodedABIBoolean(
    abiMessage: input,
    index: 5
).value()

print(flag)
// true

Author

License

Web3Swift is available under the Apache License 2.0. See the LICENSE file for more info.

web3swift's People

Contributors

abdulowork avatar driver733 avatar fossabot avatar grachyov avatar kondranton avatar m0rtymerr avatar rockfridrich avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

web3swift's Issues

Missing defensive decorators for checking signed transaction

See EthereumTransaction in bankex web3 for reference

let originalPublicKey = SECP256K1.privateToPublic(privateKey: privateKey)
        let recoveredPublicKey = self.recoverPublicKey()
        if (originalPublicKey != recoveredPublicKey) {
            return false
        }

Missing Ethereum object

I think we are missing a domain level Ethereum interface. A service I am seeking from Ethereum is getting a GasPrice object.

Missing folder structure verification

There are some issues with IDEs that cause duplication of folders and files which already leads to #62

Lets create a simple script that will verify that the root directory only contains two directories: Web3Swift and Example

2. Implement Account -> Address ERC20 tokens sending

Relates to #24

Implement logic for transaction with сustom data
Implement transaction data builder for different parameter types
Implement string ABI function checking
Implement contract calling function and response deserialization
Implement contract deployment transaction
Implement contract deployment with non empty constructor

Bad design - BlockChainState

BlockChainState is a DTO

We should probably develop 3 decorators for remote procedures instead of having 3 implementations of BlockChainState.

1. Missing sending value to Account

Relates to #1 and #13
Implement transaction sending logic from Account (private key) to any address.
Implement different strategies for transaction parameters settlement.

Missing MainnetInfuraNetwork

Use SimpleNetwork object as an implementation of Network protocol in MainnetInfuraNetwork. Create SimpleNetwork inside MainnetInfuraNetwork instead of passing it as a dependency.

Transaction signing and sending

https://github.com/ethereum/wiki/wiki/RLP

Transaction creation flow:

  1. We need to get transaction request hash from its encoded form by encoding its:
RLP.encode this [self.nonce, self.gasPrice, self.gasLimit, self.to.addressData, self.value, self.data, self.chainID!, BigUInt(0), BigUInt(0)]

and receive hash (an array of bytes)

  1. We use transaction hash in
SECP256K1.signForRecovery(hash: hash, privateKey: privateKey)

we receive a compressed version of signature from transaction hash (an array of bytes).
See #10 for reference

  1. We need to create r and s from compressed signature
SECP256K1.unmarshalSignature(signatureData: compressedSignature)
  1. Now we need to create v from last byte of compressed signature + BigInt(35)(how does that convert to Array?) + chainId + chainId

  2. At this point we have

[self.nonce, self.gasPrice, self.gasLimit, self.to.addressData, self.value, self.data, self.v, self.r, self.s]

and we use this to get RLP for signed transaction request

  1. How we convert RLPed transaction request to hex and it to node then node return transaction id (hash)

FakeEthereumNetwork is not actually a fake

FakeEthereumNetwork is actually a mainnet. We should either make it actually fake (it shouldn't communicate to any ethereum network) or rename it to KovanEthereumNetwork and make it point to the kovan testnet.

Missing documentation

We need to decide upon the documentation format, find the tool to automate documentation and document the existing instances.

Prohibit empty lines in method bodies

I suggest that we abstain from having empty lines in method bodies. We should enforce this rule through SwiftLint.

Info about why we should do this.

We could utilize this to create this rule.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.