How to Integrate with the Ethereum Blockchain using Android
How to Integrate with the Ethereum Blockchain using Android
#Installation
To interact with the Ethereum blockchain, Magic Android SDK integrates Web3j
as sub dependency.
Add the following dependencies in build.gradle
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}
#Initialization
The Magic class is the entry-point to the Magic SDK. It must be instantiated with a Magic publishable key.
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.
01class MagicDemoApp: Application() {
02
03 lateinit var magic: Magic
04 override fun onCreate() {
05 magic = Magic(this, "YOUR_PUBLISHABLE_KEY")
06 super.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
#Testnet
Goerli Block Explorer: https://goerli.etherscan.io
Goerli Testnet Faucet: https://goerlifaucet.com
01magic = Magic(this, "YOUR_PUBLISHABLE_API_KEY", Magic.Network.goerli)
#Custom Node
You can allow specific URLs to interact with the Magic SDK, such as a custom RPC URL to send transactions to your node. The Content Security Policy (CSP) of a browser dictates what resources can be loaded. If you're using a Dedicated Wallet, you can update the policy in the settings page of the dashboard with your custom URL. If you're using a Universal Wallet, please reach out to support to get your URL added.
The use of a custom node will require the RPC URL to the project's Content Security Policy from your Magic dashboard. Refer to the CSP documentation.
01magic = Magic(this, "YOUR_PUBLISHABLE_API_KEY", CustomNodeConfiguration("https://alchemy.io"))
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 URLchainId
: Your own node's chainId
Magic.EthNetwork
01enum class EthNetwork {
02 Mainnet, Goerli
03 }
#Common Methods
#Send Transaction
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
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)
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
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
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}
#Get User Info
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}
#Smart 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.
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
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 details about this section, please click here.
#Deploy Contract
When deploying contract, building or interacting with a 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.
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
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 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
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 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}