ICON Workshop — ICONex Web Connect

2infiniti
2infiniti
Nov 7, 2018 · 6 min read

In the past tutorials we have been invoking SCORE calls and making ICX transfers through hardcoded wallet files, this obviously isn’t ideal nor practical for DAPP users. Today I am going to show you how to connect to ICONex Chrome Extension from your web app, thereby enabling users to interact with your DAPP via their own wallets.

In this tutorial, since everything is frontend development, we’ll be working under one single html file, using the new ICON SDK for Javascript to invoke the core ICON Service methods.

Demo: https://dapps.icon.support/iconex-webconnect/

Image for post
Image for post

Create a new environment for our project,

# Create a new environment$ mkdir iconex-webconnect && cd iconex-webconnect
$ touch index.html

ICON SDK for Javascript can be installed via npm or you can simply include the js file via CDN host.

<script src="https://cdn.jsdelivr.net/gh/icon-project/icon-sdk-js/build/icon-sdk-js.min.js"></script>

For this exercise we’ll use bootstrap 4 to stylize the page a little.

Create the bare-bone index.html

# index.html<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>ICONex Web Connect Demo</title>
<!-- bootstrap 4 style -->
<link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.1.3/css/bootstrap.min.css">
</head>
<body>

<!-- ICON SDK for Javascript -->
<script src="http://cdn.jsdelivr.net/gh/icon-project/icon-sdk-js@latest/build/icon-sdk-js.min.js"></script>
<!-- bootstrap 4 -->
<script src="https://stackpath.bootstrapcdn.com/bootstrap/4.1.3/js/bootstrap.min.js"></script>

</body>
</html>

We need to use CustomEvent for dispatching and listening event. The type and payload of events are assigned to the detail field. type is a string of pre-defined type of events, and payload can be any type, that contains the body of your request or response. We’ll explain more by actual implementation in a bit.

We’ll start off by exploring the available types of events, they are

HAS_ACCOUNT:REQUEST_HAS_ACCOUNT, RESPONSE_HAS_ACCOUNT

HAS_ADDRESS:REQUEST_HAS_ADDRESS, RESPONSE_HAS_ADDRESS

ADDRESS:REQUEST_ADDRESS, RESPONSE_ADDRESS

JSON-RPC:REQUEST_JSON-RPC, RESPONSE_JSON-RPC

SIGNING:REQUEST_SIGNING, RESPONSE_SIGNING

This event will allow us to determine whether the client’s ICONex has any wallets. We’ll demonstrate here how to create a CustomEvent and dispatch that event with our request’s type, how to access type and payload data for the event handler to work with the data.

Access the page via a local webserver, clicking the button will invoke the event and return true if you have wallets in ICONex.

Image for post
Image for post

Following a very similar implementation to HAS_ACCOUNT, we’ll register the onclick event to the button and dispatch a custom event to request for REQUEST_HAS_ADDRESS and add this to one of our event cases.

<!-- HAS_ADDRESS -->
<div class="my-3 p-3 bg-white rounded shadow-sm">
<h5 class="pb-2 mb-3 ml-3">HAS_ADDRESS</h5>
<div class="container">
<label for="formGroupExampleInput">ICX Address: </label>
<input id="request-has-address-data" type="text" class="form-control" placeholder="hx0000000000000000000000000000000000000000"><br>
<button id="request-has-address" type="button" class="btn btn-outline-primary">Is this address in my ICONex?</button>
</div>
<div id="response-has-address" class="mt-3 ml-3">> Result : </div>
</div>
...var requestHasAddress = document.getElementById("request-has-address");
var requestHasAddressData = document.getElementById("request-has-address-data");
var responseHasAddress = document.getElementById("response-has-address");
...case "RESPONSE_HAS_ADDRESS":
responseHasAddress.innerHTML = "> Result : " + payload.hasAddress + " (" + typeof payload.hasAddress + ")";
break;
default:
...requestHasAddress.onclick = function () {
window.dispatchEvent(new CustomEvent('ICONEX_RELAY_REQUEST', {
detail: {
type: 'REQUEST_HAS_ADDRESS',
payload: requestHasAddressData.value || requestHasAddressData.placeholder
}
}))
};

We’ll allow the user to select an active wallet they wish to use, in order to make requests, this is done via REQUEST_ADDRESS. The server will respond with RESPONSE_ADDRESS.

<!-- ADDRESS -->
<div class="my-3 p-3 bg-white rounded shadow-sm">
<h5 class="pb-2 mb-3 ml-3">ADDRESS</h5>
<div class="container">
<button id="request-address" type="button" class="btn btn-outline-primary">Which wallet to use for this session?</button>
</div>
<div id="response-address" class="mt-3 ml-3">> Result : </div>
</div>
...var requestAddress = document.getElementById("request-address");
var responseAddress = document.getElementById("response-address");
...case "RESPONSE_ADDRESS":
fromAddress = payload;
responseAddress.innerHTML = "> Selected ICX Address : " + payload;
break;
default:
...requestAddress.onclick = function () {
window.dispatchEvent(new CustomEvent('ICONEX_RELAY_REQUEST', {
detail: {
type: 'REQUEST_ADDRESS'
}
}))
};

This allows us to make requests by calling ICON JSON-RPC API, note you’re required to select an ICX wallet from previous step, as we’ll be calling from the wallet you chose. We’ll demonstrate Custom, which you can use an arbitrary JSON method of your choice. ReadOnly, which calls a read-only method of a SCORE that does not cost anything. SendTransaction that calls a SCORE function (not read-only) and ICX Transfer which are transactions that’ll cost ICX so you’re required to confirm with wallet password.

var iconService = window['icon-sdk-js'];
var IconAmount = iconService.IconAmount;
var IconConverter = iconService.IconConverter;
var IconBuilder = iconService.IconBuilder;
var requestScore = document.getElementById("request-score");
var requestScoreForm = document.getElementById("request-score-form");
var responseScore = document.getElementById("response-score");
var jsonRpc0 = document.getElementById("json-rpc-0");
var jsonRpc1 = document.getElementById("json-rpc-1");
var jsonRpc2 = document.getElementById("json-rpc-2");
var jsonRpc3 = document.getElementById("json-rpc-3");
var scoreData = document.getElementById("score-data");
...case "RESPONSE_ADDRESS":
fromAddress = payload;
responseAddress.innerHTML = "> Selected ICX Address : " + payload;
jsonRpc0.disabled = false;
jsonRpc1.disabled = false;
jsonRpc2.disabled = false;
jsonRpc3.disabled = false;
break;
case "RESPONSE_JSON-RPC":
responseScore.value = JSON.stringify(payload);
break;
case "CANCEL_JSON-RPC":
responseScore.value = null;
break;
default:
...function setRequestScoreForm() {
var data = new FormData(requestScoreForm);
var type = '';
for (const entry of data) {
type = entry[1]
}
switch (type) {
case 'read-only':
var callBuilder = new IconBuilder.CallBuilder;
var readOnlyData = callBuilder
.from(fromAddress)
.to('cx43f59485bd34d0c7e9312835d65cb399f6d29651')
.method("hello")
.build();
scoreData.value = JSON.stringify({
"jsonrpc": "2.0",
"method": "icx_call",
"params": readOnlyData,
"id": 50889
});
break;

case 'send-transaction':
var callTransactionBuilder = new IconBuilder.CallTransactionBuilder;
var callTransactionData = callTransactionBuilder
.from(fromAddress)
.to("cxb20b5ff06ba50aef42c7832958af59f9ae0651e7")
.nid(IconConverter.toBigNumber(3))
.timestamp((new Date()).getTime() * 1000)
.stepLimit(IconConverter.toBigNumber(1000000))
.version(IconConverter.toBigNumber(3))
.method('createToken')
.params({
"price": IconConverter.toHex(10000),
"tokenType": IconConverter.toHex(2)
})
.build();
scoreData.value = JSON.stringify({
"jsonrpc": "2.0",
"method": "icx_sendTransaction",
"params": IconConverter.toRawTransaction(callTransactionData),
"id": 50889
});
break;

case 'icx-transfer':
var icxTransactionBuilder = new IconBuilder.IcxTransactionBuilder;
var icxTransferData = icxTransactionBuilder
.from(fromAddress)
.to("hx04d669879227bb24fc32312c408b0d5503362ef0")
.nid(IconConverter.toBigNumber(3))
.value(IconAmount.of(1, IconAmount.Unit.ICX).toLoop())
.timestamp((new Date()).getTime() * 1000)
.version(IconConverter.toBigNumber(3))
.stepLimit(IconConverter.toBigNumber(100000))
.build();
scoreData.value = JSON.stringify({
"jsonrpc": "2.0",
"method": "icx_sendTransaction",
"params": IconConverter.toRawTransaction(icxTransferData),
"id": 50889
});
break;
default:
}
}
...requestScore.onclick = function () {
responseScore.value = null;

if (!scoreData.value) {
alert('Check the param data');
return
}

var parsed = JSON.parse(scoreData.value);
if (parsed.method === "icx_sendTransaction" && !fromAddress) {
alert('Select the ICX Address');
return
}

window.dispatchEvent(new CustomEvent('ICONEX_RELAY_REQUEST', {
detail: {
type: 'REQUEST_JSON-RPC',
payload: parsed
}
}))
};

You can experiment with one of the JSON-RPC calls, we’ll select ICX transfer.

Image for post
Image for post

Make the call and you should be prompted an ICONex window, enter your password

Image for post
Image for post

Make sure you’re selecting testnet YEOUIDO for the transfer, so you don’t actually spend any real ICX

Image for post
Image for post

The amount is set in the JSON-RPC call, IconAmount.of(1, IconAmount.Unit.ICX).toLoop() which is “0xde0b6b3a7640000” in hex.

Image for post
Image for post

You can check the transaction on the live tracker.

Image for post
Image for post

REQUEST_SIGNING sends a request to sign tx hash, so user confirmation is required, RESPONSE_SIGNING will return the signature.

Make sure a wallet is selected first before you sign, then request signing

Image for post
Image for post

You’ll be prompted to enter your password to confirm, once that’s done and submitted, you’ll get the signature back.

Image for post
Image for post

The entire code,

Demo: https://dapps.icon.support/iconex-webconnect/

That’s it for this ICONex web connect tutorial. This takes our DAPPs one step closer to being more practical and user friendly. We could apply this to tutorial series part 3 — ICON Dice Roll DAPP, so the users will not have to leave the site to their ICONex to make the bets, a much friendlier experience. We could also apply this to the workshop ICON Voting, where instead of hardcoding 3 wallets for 3 votes, we could have unlimited number of votes, as long there’s more unique wallet addresses.


Follow me on Twitter for most up-to-date ICON related content: https://www.twitter.com/2infiniti

Welcome to a place where words matter. On Medium, smart voices and original ideas take center stage - with no ads in sight. Watch

Follow all the topics you care about, and we’ll deliver the best stories for you to your homepage and inbox. Explore

Get unlimited access to the best stories on Medium — and support writers while you’re at it. Just $5/month. Upgrade

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store