• This repository has been archived on 15/Dec/2023
  • Stars
    star
    106
  • Rank 316,662 (Top 7 %)
  • Language
    Swift
  • License
    Apache License 2.0
  • Created over 6 years ago
  • Updated 6 months ago

Reviews

There are no reviews yet. Be the first to send feedback to the community and the maintainers!

Repository Details

Ethereum Web3 library implementation for Swift

Version Code coverage CI Status License Follow on Twitter Join Telegram

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.

More Repositories

1

defi-sdk

DeFi SDK Makes Money Lego Work
Solidity
782
star
2

Tokenary

Swift
130
star
3

defi-sdk-js

JavaScript/TypeScript wrapper for DeFi SDK
TypeScript
47
star
4

zerion-wallet-extension

Your crypto wallet for everything onchain.
TypeScript
41
star
5

tokenbox-ico-website

TokenBox ICO website. https://tokenbox.io
HTML
30
star
6

socialite-zerion

Social OAuth Authentication for Laravel 5. Driver Zerion SSO
PHP
21
star
7

Tokenary-P2P-Exchange

Instantly trade your favorite NFTs and ERC20s with friends nearby
Swift
19
star
8

defi-sdk-examples

Examples of DeFi SDK usage in different languages
JavaScript
16
star
9

defi-sdk-py

Python wrapper for DeFi SDK
Python
15
star
10

DTX

Decentralized Token eXchange (DTX) is platform for p2p token trading on Ethereum
JavaScript
13
star
11

defi-sdk-go

Go wrapper for DeFi SDK
Go
11
star
12

the-token-fund-smart-contracts

JavaScript
10
star
13

the-token-fund-asset-parser

Python
8
star
14

defi-sdk-demo

TypeScript
6
star
15

zerion-analytics

TypeScript
6
star
16

api-docs

5
star
17

Tokenary-Web

Tokenary Web Wallet for ETH, ERC-20
Vue
5
star
18

yc2019

Swift
5
star
19

0x.swift

Swift Library for 0x protocol
Swift
4
star
20

wallet-core-ios

This repository contains Wallet Core used by Zerion iOS and macOS apps.
Swift
4
star
21

wallet-core-android

This repository contains Wallet Core used by Zerion Android.
Kotlin
3
star
22

waves-presale

Python
3
star
23

contract-template

Clean contract template with right environment
Shell
3
star
24

nexo-token-smart-contracts

JavaScript
2
star
25

simple-web3-provider

A web3 provider with retries under the hood
TypeScript
2
star
26

genesis-collection-contracts

Zerion Genesis Collection contracts
JavaScript
1
star
27

tokenbox-smart-contracts

JavaScript
1
star