How to Integrate with the Ethereum Blockchain with Magic in Android

How to Integrate with the Ethereum Blockchain with Magic in Android

#Installation

To interact with the Ethereum blockchain, Magic Android SDK integrates Web3j as sub dependency.

Add the following dependencies in build.gradle

Java
01dependencies {
02    implementation 'link.magic:magic-android:4.0.0'
03    implementation 'org.web3j:core:4.8.8-android' 
04    implementation 'org.web3j:geth:4.8.8-android' 
05}

#Initializing Provider

note

The following example uses Kotlin 1.3 and Magic Android 4.x. Android demo will be open-sourced soon. You may use Android Studio to convert Java to Kotlin or vice versa.

Kotlin
01class MagicDemoApp: Application() {
02
03    lateinit var magic: Magic
04    override fun onCreate() {
05        magic = Magic(this, "YOUR_PUBLISHABLE_KEY")
06super.onCreate()
07    }
08}
09
10// Initialize web3j
11class MagicActivity: AppCompatActivity() {
12
13    lateinit var web3j: Web3j
14    lateinit var gethWeb3j: Geth
15
16    override fun onCreate(savedInstanceState: Bundle?) {
17        super.onCreate(savedInstanceState)
18        web3j = Web3j.build(magic.rpcProvider)
19        gethWeb3j = Geth.build(magic.rpcProvider)
20    }
21}

#Use Different Networks

#Choose Different Testnet

Kotlin
01magic = Magic(this, "YOUR_PUBLISHABLE_API_KEY", Magic.Network.Goerli)

#Configure Custom Nodes

Kotlin
01magic = Magic(this, "YOUR_PUBLISHABLE_API_KEY", CustomNodeConfiguration("https://alchemy.io"))
important

Do not set the custom nodes to local IP address (E.x. "http://127.0.0.1"\), because local IP will point to the network environment inside mobile device / simulator. Try accessible IP address in the same Wifi/Internet Environment (E.x. "http://10.0.0.93:3000"\)

#Associated Class

CustomNodeConfiguration(rpcUrl: String, chainId: Int?)

  • rpcUrl :Your own node URL
  • chainId : Your own node's chainId

Magic.EthNetwork

Swift
01enum class EthNetwork {
02        Mainnet, Goerli
03 }

#Get User Info

Kotlin
01class MagicActivity: AppCompatActivity() {
02
03    lateinit var web3j: Web3j
04
05    override fun onCreate(savedInstanceState: Bundle?) {
06        super.onCreate(savedInstanceState)
07        magic = (applicationContext as MagicDemoApp).magic
08        web3j = Web3j.build(magic.rpcProvider)
09    }
10
11    // ⭐️ After user is successfully authenticated
12    fun getAccount(){
13        try {
14            val accounts = web3j.ethAccounts().sendAsync()
15
16            accounts.whenComplete { accRepsonse: EthAccounts?, error: Throwable? ->
17                if (error != null) {
18                    Log.e("MagicError", error.localizedMessage)
19                }
20                if (accRepsonse != null && !accRepsonse.hasError()) {
21                    account = accRepsonse.accounts[0]
22                    Log.d("Magic", "Your address is $account")
23                }
24            }
25        } catch (e: Exception) {
26            Log.e("Error", e.localizedMessage)
27        }
28    }
29}

#Send Transaction

Kotlin
01class MagicActivity: AppCompatActivity() {
02
03    lateinit var magic: Magic
04    lateinit var web3j: Web3j
05
06    override fun onCreate(savedInstanceState: Bundle?) {
07        super.onCreate(savedInstanceState)
08        magic = (applicationContext as MagicDemoApp).magic
09        web3j = Web3j.build(magic.rpcProvider)
10    }
11
12    // ⭐️ After user is successfully authenticated
13    fun sendTransaction(v: View) {
14        try {
15            val value: BigInteger =  Convert.toWei("0.5", Convert.Unit.ETHER).toBigInteger()
16            val transaction = createEtherTransaction(account, BigInteger("1"), BigInteger("21000"), BigInteger("21000"), account, value)
17            val receipt = web3j.ethSendTransaction(transaction).send()
18            Log.d("Transaction complete: " + receipt.transactionHash)
19        } catch (e: Exception) {
20            Log.e("Error", e.localizedMessage)
21        }
22    }
23}

#Sign Message

Magic Android SDK extends the functionality from Web3j to allow developers to sign Typed Data. You may find it in magic.web3jSigExt

#Personal Sign

Kotlin
01class MagicActivity: AppCompatActivity() {
02
03    lateinit var magic: Magic
04    lateinit var web3j: Web3j
05    lateinit var gethWeb3j: Geth
06
07    // ⭐️ After user is successfully authenticated
08    private var account: String? = null
09
10    override fun onCreate(savedInstanceState: Bundle?) {
11        super.onCreate(savedInstanceState)
12        magic = (applicationContext as MagicDemoApp).magic
13        web3j = Web3j.build(magic.rpcProvider)
14        gethWeb3j = Geth.build(magic.rpcProvider)
15    }
16
17    fun personSign(view: View) {
18        val message = "Hello from Magic!!!"
19        val personalSign: PersonalSign = gethWeb3j.personalSign(
20                message, account, "password")
21                .send()
22        Log.d("Magic", "Signed Message: " + personalSign.signedMessage)
23
24        // Recover Message
25        val recovered = gethWeb3j.personalEcRecover(message, personalSign.signedMessage).send()
26        Log.d("Magic", "Recovered Address: " + recovered.recoverAccountId)
27    }
28}

#Sign TypedData Legacy (V1)

Kotlin
01class MagicActivity: AppCompatActivity() {
02
03    lateinit var magic: Magic
04
05    // ⭐️ After user is successfully authenticated
06    private var account: String? = null
07
08    override fun onCreate(savedInstanceState: Bundle?) {
09        super.onCreate(savedInstanceState)
10        magic = (applicationContext as MagicDemoApp).magic
11    }
12
13    // Sign with EIP712 Data Field
14    fun signTypedDataLegacy(v: View) {
15        val list = listOf(
16                EIP712TypedDataLegacyFields("string", "Hello from Magic", "This message will be signed by you"),
17                EIP712TypedDataLegacyFields("uint32", "Here is a number", "90210")
18        )
19        val signature = magic.web3jSigExt.signTypedDataLegacy(account, list).send()
20        Log.d("Magic", signature.result)
21    }
22
23    // Sign with JSON String
24    fun signTypedDataLegacyJson(v: View) {
25        val jsonString = "[{\"type\":\"string\",\"name\":\"Hello from Magic\",\"value\":\"This message will be signed by you\"},{\"type\":\"uint32\",\"name\":\"Here is a number\",\"value\":\"90210\"}]"
26        val signature = magic.web3jSigExt.signTypedDataLegacy(account, jsonString).send()
27        Log.d("Magic", signature.result)
28    }
29}

#Sign Typed Data v3

Kotlin
01class MagicActivity: AppCompatActivity() {
02
03    lateinit var magic: Magic
04    lateinit var web3j: Web3j
05    lateinit var gethWeb3j: Geth
06
07    // ⭐️ After user is successfully authenticated
08    private var account: String? = null
09
10    override fun onCreate(savedInstanceState: Bundle?) {
11        super.onCreate(savedInstanceState)
12        magic = (applicationContext as MagicDemoApp).magic
13    }
14
15    fun signTypedData(v: View) {
16        val jsonString = "{\"types\":{\"EIP712Domain\":[{\"name\":\"name\",\"type\":\"string\"},{\"name\":\"version\",\"type\":\"string\"},{\"name\":\"verifyingContract\",\"type\":\"address\"}],\"Order\":[{\"name\":\"makerAddress\",\"type\":\"address\"},{\"name\":\"takerAddress\",\"type\":\"address\"},{\"name\":\"feeRecipientAddress\",\"type\":\"address\"},{\"name\":\"senderAddress\",\"type\":\"address\"},{\"name\":\"makerAssetAmount\",\"type\":\"uint256\"},{\"name\":\"takerAssetAmount\",\"type\":\"uint256\"},{\"name\":\"makerFee\",\"type\":\"uint256\"},{\"name\":\"takerFee\",\"type\":\"uint256\"},{\"name\":\"expirationTimeSeconds\",\"type\":\"uint256\"},{\"name\":\"salt\",\"type\":\"uint256\"},{\"name\":\"makerAssetData\",\"type\":\"bytes\"},{\"name\":\"takerAssetData\",\"type\":\"bytes\"}]},\"domain\":{\"name\":\"0x Protocol\",\"version\":\"2\",\"verifyingContract\":\"0x35dd2932454449b14cee11a94d3674a936d5d7b2\"},\"message\":{\"exchangeAddress\":\"0x35dd2932454449b14cee11a94d3674a936d5d7b2\",\"senderAddress\":\"0x0000000000000000000000000000000000000000\",\"makerAddress\":\"0x338be8514c1397e8f3806054e088b2daf1071fcd\",\"takerAddress\":\"0x0000000000000000000000000000000000000000\",\"makerFee\":\"0\",\"takerFee\":\"0\",\"makerAssetAmount\":\"97500000000000\",\"takerAssetAmount\":\"15000000000000000\",\"makerAssetData\":\"0xf47261b0000000000000000000000000d0a1e359811322d97991e03f863a0c30c2cf029c\",\"takerAssetData\":\"0xf47261b00000000000000000000000006ff6c0ff1d68b964901f986d4c9fa3ac68346570\",\"salt\":\"1553722433685\",\"feeRecipientAddress\":\"0xa258b39954cef5cb142fd567a46cddb31a670124\",\"expirationTimeSeconds\":\"1553808833\"},\"primaryType\":\"Order\"}"
17        val signature = magic.web3jSigExt.signTypedData(account, jsonString).send()
18        Log.d("Magic", "Signature: " + signature.result)
19    }
20}

#Sign Typed Data v4

Kotlin
01class MagicActivity: AppCompatActivity() {
02
03    lateinit var magic: Magic
04    lateinit var web3j: Web3j
05    lateinit var gethWeb3j: Geth
06
07    // ⭐️ After user is successfully authenticated
08    private var account: String? = null
09
10    override fun onCreate(savedInstanceState: Bundle?) {
11        super.onCreate(savedInstanceState)
12        magic = (applicationContext as MagicDemoApp).magic
13    }
14
15    fun signTypedDataV4(v: View) {
16        val jsonString = "{\"domain\":{\"chainId\":1,\"name\":\"Ether Mail\",\"verifyingContract\":\"0xCcCCccccCCCCcCCCCCCcCcCccCcCCCcCcccccccC\",\"version\":\"1\"},\"message\":{\"contents\":\"Hello, Bob!\",\"from\":{\"name\":\"Cow\",\"wallets\":[\"0xCD2a3d9F938E13CD947Ec05AbC7FE734Df8DD826\",\"0xDeaDbeefdEAdbeefdEadbEEFdeadbeEFdEaDbeeF\"]},\"to\":[{\"name\":\"Bob\",\"wallets\":[\"0xbBbBBBBbbBBBbbbBbbBbbbbBBbBbbbbBbBbbBBbB\",\"0xB0BdaBea57B0BDABeA57b0bdABEA57b0BDabEa57\",\"0xB0B0b0b0b0b0B000000000000000000000000000\"]}]},\"primaryType\":\"Mail\",\"types\":{\"EIP712Domain\":[{\"name\":\"name\",\"type\":\"string\"},{\"name\":\"version\",\"type\":\"string\"},{\"name\":\"chainId\",\"type\":\"uint256\"},{\"name\":\"verifyingContract\",\"type\":\"address\"}],\"Group\":[{\"name\":\"name\",\"type\":\"string\"},{\"name\":\"members\",\"type\":\"Person[]\"}],\"Mail\":[{\"name\":\"from\",\"type\":\"Person\"},{\"name\":\"to\",\"type\":\"Person[]\"},{\"name\":\"contents\",\"type\":\"string\"}],\"Person\":[{\"name\":\"name\",\"type\":\"string\"},{\"name\":\"wallets\",\"type\":\"address[]\"}]}}"
17        val signature = magic.web3jSigExt.signTypedDataV4(account, jsonString).send()
18        Log.d("Magic", "Signature: " + signature.result)
19    }
20}

#Smart Contract

#Solidity Contract

In this example, we'll be demonstrating how to use Magic with Web3j to interact with Solidity smart contracts. The simple Hello World contract allows anyone to read and write a message to it.

Javascript
01pragma solidity ^0.5.10;
02
03contract HelloWorld {
04
05  string public message;
06
07  constructor(string memory initMessage) public {
08    message = initMessage;
09  }
10
11  function update(string memory newMessage) public {
12    message = newMessage;
13  }
14}

#Create a Kotlin/Java Contract Class from ABI

Web3j supports the auto-generation of smart contract function wrappers in Java from Solidity ABI files.

To get started, you must have two files

  • ABI JSON file <Contract>.json
  • ByteCode file <Contract>.bin

#Install web3j cli-tool

01$ curl -L https://get.web3j.io | sh

You may need to install a JDK to support this library

After it has been installed to your computer, you may run the following command to check

01$ web3j version

#Create the contract class

Bash
01$ web3j solidity generate -a=./path/to/<Contract>.json -b=./path/to/<Contract>.bin -o=/output/path/ -p={packageName}

You’ll find a Contract.java file created in your output directory above. Put this file in your project, and no more changes are needed.

For more detail about this section. Please check the following link https://web3j.readthedocs.io/en/latest/smart_contracts.html#solidity-smart-contract-wrappers

#Contract Functions

When deploying contract or building contract using web3j library, Magic offers MagicTxnManager class as a default TransactionManager that helps you to avoid dealing with private keys or credentials that Contract class requires.

#Deploy Contract

Kotlin
01import link.magic.demo.contract.Contract // This is the contract class you created above
02
03class MagicActivity: AppCompatActivity() {
04
05    lateinit var magic: Magic
06    lateinit var web3j: Web3j
07
08    // ⭐️ After user is successfully authenticated
09    private var account: String? = null
10
11    override fun onCreate(savedInstanceState: Bundle?) {
12        super.onCreate(savedInstanceState)
13        magic = (applicationContext as MagicDemoApp).magic
14        web3j = Web3j.build(magic.rpcProvider)
15    }
16
17    fun deployContract(view: View) {
18        try {
19            val price = BigInteger.valueOf(22000000000L)
20            val limit = BigInteger.valueOf(4300000)
21            val gasProvider = StaticGasProvider(price, limit)
22            val contract = Contract.deploy(
23                    web3j,
24                    account?.let { MagicTxnManager(web3j, it) },
25                    gasProvider,
26                    "HELLO_WORLD_FROM_ANDROID"
27            ).send()
28            Log.d("Magic", "Deploy to" + contract.contractAddress)
29        } catch (e: Exception) {
30            Log.e("E", "error", e)
31        }
32    }
33}

#Read From Contract

Kotlin
01fun contractRead(view: View) {
02    try {
03        val price = BigInteger.valueOf(22000000000L)
04        val limit = BigInteger.valueOf(4300000)
05        val gasProvider = StaticGasProvider(price, limit)
06
07        // Contract in Rinkeby testnet
08        val contract = ExampleContract.load("0x6a2d321a3679b1b3c8a19b84e41abd11763a8ab5", web3j, account?.let { MagicTxnManager(web3j, it) }, gasProvider)
09        if (contract.isValid) {
10            val ethCall = contract.message().send()
11            Log.d("Magic", ethCall.toString())
12        } else {
13            throw Error("contract not valid")
14        }
15    } catch (e: Exception) {
16        Log.e("E", "error", e)
17    }
18}

#Write to Contract

Kotlin
01fun contractWrite(view: View) {
02    try {
03        val price = BigInteger.valueOf(22000000000L)
04        val limit = BigInteger.valueOf(4300000)
05        val gasProvider = StaticGasProvider(price, limit)
06
07        // Contract in Rinkeby testnet
08        val contract = ExampleContract.load("0x6a2d321a3679b1b3c8a19b84e41abd11763a8ab5", web3j, account?.let { MagicTxnManager(web3j, it) }, gasProvider)
09        if (contract.isValid) {
10            val ethCall = contract.update("NEW_MESSAGE_FROM_ANDROID").send()
11            Log.d("Magic", ethCall.toString())
12        } else {
13            throw Error("contract not valid")
14        }
15    } catch (e: Exception) {
16        Log.e("E", "error", e)
17    }
18}

Did you find what you were looking for?