Introduction to Algorand Development #4
This is the fourth tutorial introducing smart contracts and blockchain development on Algorand. In this article we will look at the user experience when interacting with smart contracts on the Algorand blockchain, using Algosigner and WalletConnect.
In this tutorial, you’ll learn how to interact with smart contracts on the Algorand blockchain from a user perspective. Therefore, you’ll learn to use AlgoSigner and WalletConnect to create interfaces users can use to sign transactions quickly without using any code or tools.
In this fourth post, you’ll learn the following skills:
- A quick introduction to smart contracts (and how to deploy/interact with them)
- Introduction to AlgoSigner and WalletConnect
- How to set up AlgoSigner to interact with a smart contract
- How to set up WalletConnect to interact with a smart contract
- Conclusion
- Demo code
Requirements
- A running Algorand sandbox or Algorand node. More information can be found here
- At least one funded addresses in the sandbox
- `goal` ((part of the Algorand sandbox by default))
- `python3`
- `Chrome` webbrowser
- `Algorand Wallet` installed on your smartphone
Background
Algorand and the ecosystem surrounding it provide many tools and development libraries which make interfacing with Algorand easy for a software developer. However, it is likely that the end user of a given Algorand application will not be a software engineer, but rather a normal individual, possibly with limited technical knowledge.
The tools and libraries that are easy for a software engineer to use may be confusing and even prohibitive for the average user. For this reason, there are several practical and intuitive Algorand user interfaces (UIs) to interact with the Algorand blockchain and its smart contracts. Two that will be explored in this tutorial are AlgoSigner and WalletConnect.
AlgoSigner is a Chrome browser-extension which implements a minimal Algorand wallet in-browser — like MetaMask for Algorand. AlgoSigner also provides a developer API to be integrated into a websites’s source code. The website displays a simplistic UI and uses the AlgoSigner API to connect to the in-browser wallet and perform Algorand operations such as sending transactions and performing smart contract calls. To the end user, they only have to interface with a sleek web-UI in order to use an Algorand DApp.
WalletConnect is slightly different than AlgoSigner in that it is just a set of developer APIs. These APIs enable a website to integrate WalletConnect into its source code and communicate with the official Algorand smartphone wallet. Now, similarly like with AlgoSigner, a website would provide a UI and then uses the WalletConnect API to perform the various desired Algorand operations.
When an Algorand dapp uses either AlgoSigner or WalletConnect, the end user’s experience can be as simple as clicking a few buttons on a website and authorizing the transaction. Contrasting that with the user experience using developer tools such as `goal`, which are certainly more versatile and advanced, the barrier to entry would certainly dissuade many non-technical users from using Algorand or Algorand dapps.
Stateful Counter Smart Contract Example
This tutorial will showcase AlgoSigner and WalletConnect by interacting with a basic stateful counter dapp. The dapp is extremely simple. It has a global counter which, upon every invocation of the smart contract, gets incremented by one.
Note: This example is provided for informational purposes only. Therefore, for simplicity purposes and cleanliness, it contains only essential code blocks and lacks many of the necessary security checks listed here.
Make sure to create a new file in the assets directory in your sandbox (`/sandbox/assets`) called `stateful_counter.py` and add the below code.
```py
from pyteal import *
def counter_program():
"""
This smart contract implements a stateful counter
"""
var_counter = Bytes("counter")
counter = App.globalGet(var_counter)
init_counter = Seq([
App.globalPut(var_counter, Int(0)),
Return(Int(1))
])
add_one = Seq([
App.globalPut(var_counter, counter + Int(1)),
Return(Int(1))
])
program = Cond(
[Txn.application_id() == Int(0), init_counter],
[Txn.on_completion() == OnComplete.NoOp, add_one],
[Int(1), Return(Int(1))]
)
return program
if __name__ == "__main__":
print(compileTeal(counter_program(), Mode.Application, version=5))
```
Since this program requires persistent variables, it is compiled with the mode set to `Mode.Application` indicating that it is a smart contract. In a previous tutorial explaining PyTEAL, smart signatures were incorporated in the code examples. These are slightly different and should not be confused with smart contracts like the one above. Most crucially, smart contracts can maintain a persistent state, global and local variables that remember their values between application calls.
This tutorial will explain the main components of the above smart contract in order to better understand how it works. More detailed information regarding the life cycle of an Algorand smart contract may be found here. The global variable is called `”counter”` and can be easily accessed with `App.globalGet` and assigned with `App.globalPut`. The Python variable named `counter` conveniently maps to the getter function.
The entire smart contract is encapsulated in the `Cond` expression assigned to `program`. When a smart contract is initially created, it is invoked with a `Txn.application_id()` of 0. The first condition in the `Cond` catches this case and redirects the program to `init_counter`. This is a simple code block which first sets the counter to its initial value of 0 and then returns 1, indicating success. In later invocations, the smart contract will have its ID already set to some non-zero value, so the first condition will never be invoked again.
In order to call this smart contract, a user would submit a so-called `NoOp` transaction. One can pass arguments in this `NoOp` transaction, but for this particular use case, they are not necessary. The second condition in the `Cond` catches this case and runs the `add_one` code block. As its name implies, the `add_one` code block increments the counter’s value by 1 and exits indicating success.
The third condition of the `Cond` is a catch-all case where if neither of the two preceding cases were triggered, then the smart contract just exits successfully. Warning: Doing this is very dangerous and introduces many security vulnerabilities. There are other application calls possible within the life cycle of a smart contract and this condition will gladly execute and accept all of them.
**Note:** The below `clear_program.py` is a necessary helper application required by all Algorand smart contracts in order to be deployed. It should contain any clean up code to execute whenever a user wants to opt-out from a smart contract. In this example, there is nothing to clean up, so the clear program simply returns successfully and no more. More information on clear programs and the overall smart contract lifecycle can be found here.
Make a new file called `clear_program.py` in your assets directory and add the below code.
```py
from pyteal import *
def clear_program():
"""
Nothing to clear. Simply exit successfully.
"""
return Return(Int(1))
if __name__ == '__main__':
print(compileTeal(clear_program(), Mode.Application, version=5))
```
Even though this smart contract is very simple to understand, let’s understand what is its expected behavior better by using `goal` to deploy and call it.
Goal Demo: Compile the Smart Contract
Let’s first compile the contracts from PyTeal to TEAL (from the root of your sandbox)
```sh
python3 ./assets/stateful_counter.py > ./artifacts/stateful_counter.teal
python3 ./assets/clear_program.py > ./artifacts/clear_program.teal
```
Next, we need to deploy it to the sandbox.
Goal Demo: Deploy the Smart Contract
To access the TEAL contracts in the node, we have to copy them into our sandbox.
```sh
./sandbox copyTo ./artifacts/stateful_counter.teal
./sandbox copyTo ./artifacts/clear_program.teal
```
In order to deploy a smart contract, the creator and data requirements of the smart contract must be specified to `goal`. None of these parameters may be changed once the smart contract has been deployed. This is important since nothing can be done if more global or local data is needed in an updated release of the smart contract code. The current smart contract only requires one global integer value, the counter. The creator is a funded address imported into `goal` with the address “XEMQAYBJNX7XZSRSS6CEV6PMGD2Y7BLI46VFQZ2MULRMEGRTQUUS6ZXRFE”. You can use any account from the previous tutorials that has sufficient funds. To list all accounts for your sandbox, use `./sandbox goal account list`.
```sh
./sandbox goal app create --creator XEMQAYBJNX7XZSRSS6CEV6PMGD2Y7BLI46VFQZ2MULRMEGRTQUUS6ZXRFE --global-byteslices 0 --global-ints 1 --local-byteslices 0 --local-ints 0 --approval-prog stateful_counter.teal --clear-prog clear_program.teal
```
The output of the deployment includes the application ID (also known as application index) of this smart contract. This is important to remember since it is a required parameter of an application call in order to invoke the appropriate smart contract.
An example output from deploying this smart contract is:
```sh
Attempting to create app (approval size 49, hash P3JCINPYNWCX54B6OCN4HWXTLJ2ZUPORDFMBPHPJECVIZNHRS4VQ; clear size 4, hash BJATCHES5YJZJ7JITYMVLSSIQAVAWBQRVGPQUDT5AZ2QSLDSXWWA)
Issued transaction from account XEMQAYBJNX7XZSRSS6CEV6PMGD2Y7BLI46VFQZ2MULRMEGRTQUUS6ZXRFE, txid OMURGA4H5E7EDQPRWQDUEGX53CDVQEDNUOEZVOIOBYJH6H54QSKQ (fee 1000)
Transaction OMURGA4H5E7EDQPRWQDUEGX53CDVQEDNUOEZVOIOBYJH6H54QSKQ still pending as of round 17876540
Transaction OMURGA4H5E7EDQPRWQDUEGX53CDVQEDNUOEZVOIOBYJH6H54QSKQ still pending as of round 17876540
Transaction OMURGA4H5E7EDQPRWQDUEGX53CDVQEDNUOEZVOIOBYJH6H54QSKQ committed in round 17876540
Created app with app index 43987809
```
The application index used during this tutorial for the “Add One” smart contract is `43987809`.
Goal Demo: Call the Smart Contract
Any funded user can invoke this smart contract to increment the counter. In this example, since the creator is already funded, that account will be the one which will invoke the smart contract.
```sh
./sandbox goal app call --from XEMQAYBJNX7XZSRSS6CEV6PMGD2Y7BLI46VFQZ2MULRMEGRTQUUS6ZXRFE --app-id 43987809
```
An example output showing this application call be accepted into the Algorand network is:
```
Issued transaction from account XEMQAYBJNX7XZSRSS6CEV6PMGD2Y7BLI46VFQZ2MULRMEGRTQUUS6ZXRFE, txid XUH4EQYT6YQVBBOH5JZ2RTZ74BV7JOUUMGTEP653BL2YAJOAHT6A (fee 1000)
Transaction XUH4EQYT6YQVBBOH5JZ2RTZ74BV7JOUUMGTEP653BL2YAJOAHT6A still pending as of round 17876594
Transaction XUH4EQYT6YQVBBOH5JZ2RTZ74BV7JOUUMGTEP653BL2YAJOAHT6A still pending as of round 17876595
Transaction XUH4EQYT6YQVBBOH5JZ2RTZ74BV7JOUUMGTEP653BL2YAJOAHT6A committed in round 17876596
```
Goal Demo: Read the Global Counter Value
To confirm that the “Add One” smart contract is exhibiting its correct behavior, the global state can be queried from the Algorand network.
```sh
./sandbox goal app read --global --app-id 43987809
```
An example output showing the counter value is:
```json
{
"counter": {
"tt": 2,
"ui": 1
}
}
```
The value is in the “ui” field which stands for unsigned integer. You may call the smart contract a few more times and re-read the global counter state to see whether it has been incremented correctly.
AlgoSigner
To reiterate AlgoSigner and its purpose, it is a browser-based Algorand wallet which also provides a developer API to websites. Websites use the API to interface with the in-browser wallet in order to sign transactions and perform any requested dapp operations. To the end user, they would be simply clicking buttons on a website’s UI and authorize any transaction in the extension itself.
This section will explain how to use AlgoSigner to integrate the above smart contract into a website DApp. It will cover how the website uses the AlgoSigner API to invoke the smart contract and will show screenshots of what the end user sees in the process. This tutorial will utilize the TestNet for all of the smart contract operations. If you are following this tutorial, be sure to also be utilizing the TestNet, so that you do not accidentally spend real Algos.
The full source code of the AlgoSigner demo app which you can run on your won can be found on GitHub.
AlgoSigner User Setup
In order to use an AlgoSigner enabled website dapp, the end user must set up AlgoSigner on their Chrome web-browser. This process is simple and intuitive. The Chrome browser add-on may be downloaded from here.
Once installed, you can set up a password for the AlgoSigner Browser Wallet.
Since this tutorial uses the TestNet, be sure to change the wallet to utilize the TestNet.
Then, you may add an account by clicking the orange button displaying “Add account”. Here, you will be presented with several options, from creating a new account to importing an existing one. If you already have an Algorand testnet account, such as via the sandbox setup, all you need to do is enter the 25 word wallet passphrase into AlgoSigner.
To get the wallet passphrase via `goal`, in this example for an account with the address “XEMQAYBJNX7XZSRSS6CEV6PMGD2Y7BLI46VFQZ2MULRMEGRTQUUS6ZXRFE”, run:
```sh
./sandbox goal account export -a XEMQAYBJNX7XZSRSS6CEV6PMGD2Y7BLI46VFQZ2MULRMEGRTQUUS6ZXRFE
```
The output should be:
```sh
Exported key for account XEMQAYBJNX7XZSRSS6CEV6PMGD2Y7BLI46VFQZ2MULRMEGRTQUUS6ZXRFE: "<25 WORD PASSPHRASE HERE>".
```
These words can be copied into AlgoSigner’s respective text box here:
AlgoSigner Website API
Connecting to the API
The first step a website which uses AlgoSigner needs to do is connect to the AlgoSigner API.
```js
AlgoSigner.connect()
.then((d) => {
...
})
.catch((e) => {
console.error(e);
});
```
The user will be presented with a pop-up to authorize the connection to AlgoSigner.
Once connected successfully, the website may use the other AlgoSigner API calls to interact with AlgoSigner and issue transactions to the Algorand network.
Setting up the Algod Client
The next step is to connect to a remote Algorand node. Since the AlgoSigner is a lightweight wallet, any requests regarding the current status of the Algorand network, such as what the current round is or whether a transaction has been confirmed, must be queried through Algod and go through a remote Algorand server. If you are using the Algorand sandbox with a connection to the TestNet network, then Algod can connect with this node.
This tutorial uses the sandbox to connect to the TestNet. The sandbox node exposes an address and token for the Algod to connect to. Normally, the default address value is “http://localhost” at port 4001 and the default token value is “aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa”.
To double-check, you can see the actual values of the node from within the sandbox:
```sh
# Enter the sanbox algod environment
./sandbox enter algod
# List the exposed address and port
cat ~/testnetwork/Node/algod.net
# Outputs: [::]:4001 (equivalent to http://localhost:4001)
# List the token
cat ~/testnetwork/Node/algod.token
# Outputs: aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
```
The code to connect to Algod is as follows:
```js
const algodServer = 'http://localhost';
const token = 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa';
const port = '4001';
algodClient = new algosdk.Algodv2(token, algodServer, port);
algodClient.healthCheck().do()
.then(d => {
...
})
.catch(e => {
console.error(e);
});
```
Alternatively, there are services that provide access to an Algorand node, oftentimes for free. To use them, you would need to follow their instructions and input the appropriate `algodServer`, `token` and `port` values.
Get TestNet Accounts
This step is not strictly required for the simple smart contract DApp. Nevertheless, it is helpful to retrieve the current accounts maintained by the installed AlgoSigner wallet to pre-populate the later parts of the DApp UI.
```js
AlgoSigner.accounts({
ledger: 'TestNet'
})
.then((accounts) => {
console.log(accounts);
})
.catch((e) => {
console.error(e);
});
```
At the time of writing this tutorial, there were three accounts already imported into the wallet. The console output shows the three distinct addresses.
```sh
[
{
"address": "XEMQAYBJNX7XZSRSS6CEV6PMGD2Y7BLI46VFQZ2MULRMEGRTQUUS6ZXRFE"
},
{
"address": "TJMHAAEFTWVZNA2O52AA57BWVCJOBSEIU2OEWNWADVM5F5Y2DRJDWMDGRQ"
},
{
"address": "KJKRDF4RHNRBYHZXMPTCBNFU64B6KVF3RD3446YAAFTXF6ICTQ3WT5F7UE"
}
]
```
Get the Suggested Transaction Parameters
Every Algorand transaction has a few parameters that for most use-cases, should just be set to their suggested values. This smart contract does not require any specific parameters and so will use the suggested ones. One part of the suggested parameters is the transaction validity window — from which round until which round is this transaction valid to be accepted by the Algorand network. This data requires knowledge of the current Algorand network’s state and thus must be queried with the Algod client.
```js
algodClient.getTransactionParams().do()
.then((d) => {
suggestedTxParams = d;
})
.catch((e) => {
console.error(e);
});
```
This is an example of suggested transaction parameters output from the above code block when you execute the JavaScript file.
```json
{
"flatFee": false,
"fee": 0,
"firstRound": 17877161,
"lastRound": 17878161,
"genesisID": "testnet-v1.0",
"genesisHash": "SGO1GKSzyE7IEPItTxCByw9x8FmnrCDexi9/cookie="
}
```
There you can see the `firstRound` and `lastRound` parameters which indicate the rounds in between which this transaction must be submitted in order to be committed into the Algorand network. If the transaction is sent in a current round which is outside that window, it will be rejected automatically.
Sign a Transaction Invoking the Smart Contract
The next step is to create a transaction object and have it signed by AlgoSigner. In order for AlgoSigner to sign the transaction, it displays to the user a popup window asking for explicitly authorization before any signing proceeds.
The transaction object is created with the Algorand Javascript SDK, not AlgoSigner itself. In this case, since the smart contract accepted application calls with no arguments, the transaction must only include which account is issuing it (the `from` field) and which smart contract to invoke (the `app-index` field).
```js
const txn = algosdk.makeApplicationNoOpTxnFromObject({
from: document.getElementById('from').value,
appIndex: +document.getElementById('app-index').value,
suggestedParams: {...suggestedTxParams}
});
// Use the AlgoSigner encoding library to make the transactions base64
let txn_b64 = AlgoSigner.encoding.msgpackToBase64(txn.toByte());
```
In order to input these fields, the user could see a UI as such:
In this case, the `from` is “XEMQAYBJNX7XZSRSS6CEV6PMGD2Y7BLI46VFQZ2MULRMEGRTQUUS6ZXRFE” and the `app-index` is “43987809”.
AlgoSigner can understand the base64 encoded transaction object about to be signed.
```js
AlgoSigner.signTxn([{txn: txn_b64}])
.then((d) => {
signedTxs = d;
})
.catch((e) => {
console.error(e);
});
```
Before the signing proceeds, the user is presented a pop-up window where they must authorize the operation.
Once signed, the transaction is ready to be sent into the network. A signed transaction is not human intelligible, but an example signed transaction is as follows:
```sh
[
{
"txID": "FQ2WKTQ5ZOXY4XV3B6VKST2JSFQIGP3WTFCJNYPB4S7FU2LO37HA",
"blob": "gqNzaWfEQEhmYVoAVy4+KJhFZo+JFSpjdnXaKERXXmrWp26qQerxWaY7dItTAji2BiwBTQlZkY5dai3Wqp1HEF+6U2pJ2QWjdHhuiKRhcGlkzgKfM2GjZmVlzQPoomZ2zgEQyKmjZ2VurHRlc3RuZXQtdjEuMKJnaMQgSGO1GKSzyE7IEPItTxCByw9x8FmnrCDexi9/cOUJOiKibHbOARDMkaNzbmTEILkZAGApbf98yjKXhEr57DD1j4Vo56pYZ0yi4sIaM4UppHR5cGWkYXBwbA=="
}
]
```
Send the Signed Transaction
The next logical step is to send the signed transaction into the Algorand network. The AlgoSigner provides a helper function for this.
```js
AlgoSigner.send({
ledger: 'TestNet',
tx: signedTxs[0].blob
})
.then((d) => {
tx = d;
})
.catch((e) => {
console.error(e);
});
```
The result is the transaction ID, simply confirming that the transaction was sent into the network.
```json
{
"txId": "FQ2WKTQ5ZOXY4XV3B6VKST2JSFQIGP3WTFCJNYPB4S7FU2LO37HA"
}
```
Note: a transaction being sent into the network does not mean that it will be ultimately accepted. That must be checked by querying the status of the pending transaction.
Check the Pending Transaction Status
It may be useful as feedback to the end user to inquire about the status of the sent transaction from the prior step. This utilizes the Algod client since this is an operation which relies on the Algorand network’s state.
```js
algodClient.pendingTransactionInformation(tx.txId).do()
.then((status) => {
console.log(status);
})
.catch((e) => {
console.error(e);
});
Before the transaction has been confirmed, only the transaction metadata will be included in the status output.
```json
{
"pool-error": "",
"txn": {
"sig": "NzIsMTAyLDk3LDkwLDAsODcsNDYsNjIsNDAsMTUyLDY5LDEwMiwxNDMsMTM3LDIxLDQyLDk5LDExOCwxMTcsMjE4LDQwLDY4LDg3LDk0LDEwNiwyMTQsMTY3LDExMCwxNzAsNjUsMjM0LDI0MSw4OSwxNjYsNTksMTE2LDEzOSw4MywyLDU2LDE4Miw2LDQ0LDEsNzcsOSw4OSwxNDUsMTQyLDkzLDEwNiw0NSwyMTQsMTcwLDE1Nyw3MSwxNiw5NSwxODYsODMsMTA2LDczLDIxNyw1",
"txn": {
"apid": 43987809,
"fee": 1000,
"fv": 17877161,
"gen": "testnet-v1.0",
"gh": "NzIsOTksMTgxLDI0LDE2NCwxNzksMjAwLDc4LDIwMCwxNiwyNDIsNDUsNzksMTYsMTI5LDIwMywxNSwxMTMsMjQwLDg5LDE2NywxNzIsMzIsMjIyLDE5OCw0NywxMjcsMTEyLDIyOSw5LDU4LDM0",
"lv": 17878161,
"snd": "XEMQAYBJNX7XZSRSS6CEV6PMGD2Y7BLI46VFQZ2MULRMEGRTQUUS6ZXRFE",
"type": "appl"
}
}
}
```
Once the transaction has been confirmed, the status output will include the `confirmed-round`. As long as this value is non-zero, that means that the transaction has been accepted into the Algorand network at that given round in the Algorand blockchain.
```json
{
"confirmed-round": 17877359,
"global-state-delta": [
{
"key": "Y291bnRlcg==",
"value": {
"action": 2,
"uint": 12
}
}
],
"pool-error": "",
"txn": {
"sig": "NzIsMTAyLDk3LDkwLDAsODcsNDYsNjIsNDAsMTUyLDY5LDEwMiwxNDMsMTM3LDIxLDQyLDk5LDExOCwxMTcsMjE4LDQwLDY4LDg3LDk0LDEwNiwyMTQsMTY3LDExMCwxNzAsNjUsMjM0LDI0MSw4OSwxNjYsNTksMTE2LDEzOSw4MywyLDU2LDE4Miw2LDQ0LDEsNzcsOSw4OSwxNDUsMTQyLDkzLDEwNiw0NSwyMTQsMTcwLDE1Nyw3MSwxNiw5NSwxODYsODMsMTA2LDczLDIxNyw1",
"txn": {
"apid": 43987809,
"fee": 1000,
"fv": 17877161,
"gen": "testnet-v1.0",
"gh": "NzIsOTksMTgxLDI0LDE2NCwxNzksMjAwLDc4LDIwMCwxNiwyNDIsNDUsNzksMTYsMTI5LDIwMywxNSwxMTMsMjQwLDg5LDE2NywxNzIsMzIsMjIyLDE5OCw0NywxMjcsMTEyLDIyOSw5LDU4LDM0",
"lv": 17878161,
"snd": "XEMQAYBJNX7XZSRSS6CEV6PMGD2Y7BLI46VFQZ2MULRMEGRTQUUS6ZXRFE",
"type": "appl"
}
}
}
```
Read the Smart Contract State
The purpose of the “Add One” smart contract used in this example is to store a global counter variable and increment it every time the smart contract is invoked. This step uses the Algod client to read the global state of the counter variable directly from the Algorand network. This way, one can be sure that sending the transaction application call from above is actually having some effect.
```js
algodClient.getApplicationByID(appIndex).do()
.then((d) => {
const globalState = d['params']['global-state'];
})
.catch((e) => {
console.error(e);
})
```
An example output is:
```sh
[
{
"key": "Y291bnRlcg==",
"value": {
"bytes": "",
"type": 2,
"uint": 12
}
}
]
```
This shows that the counter variable’s value is 12. Sending a new application call transaction again by following the above steps will show that the counter has been incremented. This is inline with the expected behavior of the smart contract.
```sh
[
{
"key": "Y291bnRlcg==",
"value": {
"bytes": "",
"type": 2,
"uint": 13
}
}
]
```
WalletConnect
WalletConnect serves a similar end goal as AlgoSigner of providing an easy-to-use interface to interacting with Algorand DApps. WalletConnect is an API to enable website DApps to connect to and interact with the official Algorand smartphone wallet. Similarly like with AlgoSigner, a website would provide a UI and use the WalletConnect API to perform the various Algorand operations. These operations, such as signing a transaction, are all actually executed on the Algorand smartphone application. To the end user, they would be prompted on their smartphone to authorize the various requested operations.
This section will explain how to use WalletConnect to integrate the “Add One” smart contract example from above into a sample website DApp. It will also show screenshots of what the end user would see and do in order to interact with the DApp.
The full source code of the WalletConnect demo app which you can run on your won can be found on GitHub.
WalletConnect User Setup
In order to use a WalletConnect enabled website, the end user must have an Algorand wallet setup on their smartphone. This process is simple and intuitive. The wallet application may be downloaded from both the Google and Apple app stores for free.
This tutorial uses accounts on the TestNet. If you are following along, it is recommended to also use the TestNet since you will not be using real Algos when experimenting and playing around. To set up a TestNet account on the Algorand smartphone wallet, you will need to go to the “Developer Settings” -> “Node Settings” and select the TestNet network.
Once selected, you can create a TestNet account and populate it with a few TestNet Algos for free. TestNet Algos may be acquired from the TestNet dispenser here.
WalletConnect Website API
Many of the steps in integrating the “Add One” smart contract with WalletConnect parallel those of AlgoSigner. Therefore, some of the steps here will simply reference the AlgoSigner’s respective steps in order to avoid unnecessary repetition.
Initializing the API
The first step is to initialize the WalletConnect API. This setup provides WalletConnect with the appropriate parameters in order to communicate with the Algorand smartphone wallet.
```js
// Create a `walletConnector`
walletConnector = new WalletConnect({
bridge: "https://bridge.walletconnect.org",
qrcodeModal: WalletConnectQRCodeModal,
});
// Check if this is a new connection
if (!walletConnector.connected) {
// Create new session
walletConnector.createSession();
}
```
The above code will generate a QR code and display it to the user. For the user to connect the Algorand smartphone wallet to WalletConnect, they simply must scan this QR code from within the app.
Once connected successfully, the website DApp may use WalletConnect to connect to the user’s smartphone wallet in order to perform signing and other privileged operations.
Setting up the Algod Client
This section is identical to the respective section for AlgoSigner.
For reference, the code to connect to Algod is as follows:
```js
const algodServer = 'http://localhost';
const token = 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa';
const port = '4001';
algodClient = new algosdk.Algodv2(token, algodServer, port);
algodClient.healthCheck().do()
.then(d => {
...
})
.catch(e => {
console.error(e);
});
```
Get TestNet Accounts
This section is similar to the respective section for AlgoSigner. Retrieving the TestNet accounts is not strictly necessary for this simple smart contract DApp. Nevertheless, it is helpful to have in order to pre-populate the later parts of the DApp UI.
Once the WalletConnect API was initialized and connected to the Algorand smartphone wallet, the returned object, contains the connected TestNet accounts from the smartphone wallet. In the above step, the WalletConnect object is saved to a variable named `walletConnector`. The addresses may be extracted as follows:
```js
if (walletConnector.connected) {
const { accounts } = walletConnector;
console.log(accounts);
}
```
At the time of writing this tutorial, there was one account in the Algorand smartphone wallet. Therefore, the console output shows this address.
```sh
[
"XEMQAYBJNX7XZSRSS6CEV6PMGD2Y7BLI46VFQZ2MULRMEGRTQUUS6ZXRFE"
]
```
Get the Suggested Transaction Parameters
This section is identical to the respective section for AlgoSigner.
For reference, the code to get the suggested transaction parameters is as follows:
```js
algodClient.getTransactionParams().do()
.then((d) => {
suggestedTxParams = d;
})
.catch((e) => {
console.error(e);
});
```
A sample output of suggested transaction parameters is:
```json
{
"flatFee": false,
"fee": 0,
"firstRound": 17877161,
"lastRound": 17878161,
"genesisID": "testnet-v1.0",
"genesisHash": "SGO1GKSzyE7IEPItTxCByw9x8FmnrCDexi9/cookie="
}
```
Sign a Transaction Invoking the Smart Contract
The first step in signing a transaction is to create the transaction object. Part of the transaction object includes a message that is displayed to the user on their smartphone wallet. Ideally, this message would be a description of the transaction to be signed.
```js
const txn = algosdk.makeApplicationNoOpTxnFromObject({
from: document.getElementById('from').value,
appIndex: appIndex,
suggestedParams: {...suggestedTxParams}
});
txns_b64 = [
{
txn: arrayBufferToBase64(algosdk.encodeUnsignedTransaction(txn)),
message: 'Invoke smart contract call to add one',
},
];
```
This transaction object is then encapsulated as a JSON RPC request with the identifying tag “algo_signTxn”. This request is simply sent using the WalletConnect and is interpreted by the smartphone wallet.
```js
const requestParams = [txns_b64];
const request = JsonRpcUtils.formatJsonRpcRequest("algo_signTxn", requestParams);
signedTxns = await walletConnector.sendCustomRequest(request);
```
The Algorand wallet sees the tag and initiates the transaction signing processes. The first step of this processes, before the wallet performs any signing, is to seek authorization from the user.
A signed transaction is not human intelligible, but an example signed transaction is as follows:
```sh
[
"MTMwLDE2MywxMTUsMTA1LDEwMywxOTYsNjQsNDgsMTQ4LDQ5LDM1LDEyOSwxMjYsNDUsMTkyLDIzNiwyMDUsMjAsNTEsMTQsMjE5LDEyLDEyLDUsMTMzLDE4NSwxNjAsMjQ2LDEzNywyNDgsOTAsMjM4LDEyNywxNCwxOTUsMjU1LDQ1LDEzMyw2NSwyMjgsOTksMTk1LDE1LDM2LDg2LDUwLDAsMTg5LDE5NiwxNjgsMTUxLDIyNSwxMjUsNTMsODAsMTk3LDk4LDQzLDM5LDMwLDE0MCwyMDAsMjQsMzQsMTMsNzAsNzEsOTAsMTc2LDYwLDgsMTYzLDExNiwxMjAsMTEwLDEzNiwxNjQsOTcsMTEyLDEwNSwxMDAsMjA2LDIsMTU5LDUxLDk3LDE2MywxMDIsMTAxLDEwMSwyMDUsMywyMzIsMTYyLDEwMiwxMTgsMjA2LDEsMTcsMTgsMjEyLDE2MywxMDMsMTAxLDExMCwxNzIsMTE2LDEwMSwxMTUsMTE2LDExMCwxMDEsMTE2LDQ1LDExOCw0OSw0Niw0OCwxNjIsMTAzLDEwNCwxOTYsMzIsNzIsOTksMTgxLDI0LDE2NCwxNzksMjAwLDc4LDIwMCwxNiwyNDIsNDUsNzksMTYsMTI5LDIwMywxNSwxMTMsMjQwLDg5LDE2NywxNzIsMzIsMjIyLDE5OCw0NywxMjcsMTEyLDIyOSw5LDU4LDM0LDE2MiwxMDgsMTE4LDIwNiwxLDE3LDIyLDE4OCwxNjMsMTE1LDExMCwxMDAsMTk2LDMyLDE4NSwyNSwwLDk2LDQxLDEwOSwyNTUsMTI0LDIwMiw1MCwxNTEsMTMyLDc0LDI0OSwyMzYsNDgsMjQ1LDE0MywxMzMsMTA0LDIzMSwxNzAsODgsMTAzLDc2LDE2MiwyMjYsMTk0LDI2LDUxLDEzMyw0MSwxNjQsMTE2LDEyMSwxMTIsMTAxLDE2NCw5NywxMTIsMTEyLDEwOA=="
]
```
Send the Signed Transaction
The returned signed transaction can be interpreted and sent into the Algorand network directly with the Algod client.
```js
algodClient.sendRawTransaction(new Uint8Array(signedTxns[0])).do()
.then((d) => {
tx = d;
})
.catch((e) => {
console.error(e);
});
```
The result is the transaction ID, simply confirming that the transaction was sent into the network.
```
{
"txId": "L4SRYCZ47WD6IPG4KHII6AZE3YUOIZVTPLVR3UCFMA7CA37M4JZQ"
}
```
Note: a transaction being sent into the network does not mean that it will be ultimately accepted. That must be checked by querying the status of the pending transaction.
Check the Pending Transaction Status
This section is identical to the respective section for AlgoSigner.
For reference, the code to check the pending transaction status is as follows:
```js
algodClient.pendingTransactionInformation(tx.txId).do()
.then((status) => {
console.log(status);
})
.catch((e) => {
console.error(e);
});
```
A sample output of a pending transaction yet to be confirmed is:
```json
{
"pool-error": "",
"txn": {
"sig": "NzIsMTAyLDk3LDkwLDAsODcsNDYsNjIsNDAsMTUyLDY5LDEwMiwxNDMsMTM3LDIxLDQyLDk5LDExOCwxMTcsMjE4LDQwLDY4LDg3LDk0LDEwNiwyMTQsMTY3LDExMCwxNzAsNjUsMjM0LDI0MSw4OSwxNjYsNTksMTE2LDEzOSw4MywyLDU2LDE4Miw2LDQ0LDEsNzcsOSw4OSwxNDUsMTQyLDkzLDEwNiw0NSwyMTQsMTcwLDE1Nyw3MSwxNiw5NSwxODYsODMsMTA2LDczLDIxNyw1",
"txn": {
"apid": 43987809,
"fee": 1000,
"fv": 17877161,
"gen": "testnet-v1.0",
"gh": "NzIsOTksMTgxLDI0LDE2NCwxNzksMjAwLDc4LDIwMCwxNiwyNDIsNDUsNzksMTYsMTI5LDIwMywxNSwxMTMsMjQwLDg5LDE2NywxNzIsMzIsMjIyLDE5OCw0NywxMjcsMTEyLDIyOSw5LDU4LDM0",
"lv": 17878161,
"snd": "XEMQAYBJNX7XZSRSS6CEV6PMGD2Y7BLI46VFQZ2MULRMEGRTQUUS6ZXRFE",
"type": "appl"
}
}
}
```
A sample output of a confirmed transaction is:
```json
{
"confirmed-round": 17877359,
"global-state-delta": [
{
"key": "Y291bnRlcg==",
"value": {
"action": 2,
"uint": 12
}
}
],
"pool-error": "",
"txn": {
"sig": "NzIsMTAyLDk3LDkwLDAsODcsNDYsNjIsNDAsMTUyLDY5LDEwMiwxNDMsMTM3LDIxLDQyLDk5LDExOCwxMTcsMjE4LDQwLDY4LDg3LDk0LDEwNiwyMTQsMTY3LDExMCwxNzAsNjUsMjM0LDI0MSw4OSwxNjYsNTksMTE2LDEzOSw4MywyLDU2LDE4Miw2LDQ0LDEsNzcsOSw4OSwxNDUsMTQyLDkzLDEwNiw0NSwyMTQsMTcwLDE1Nyw3MSwxNiw5NSwxODYsODMsMTA2LDczLDIxNyw1",
"txn": {
"apid": 43987809,
"fee": 1000,
"fv": 17877161,
"gen": "testnet-v1.0",
"gh": "NzIsOTksMTgxLDI0LDE2NCwxNzksMjAwLDc4LDIwMCwxNiwyNDIsNDUsNzksMTYsMTI5LDIwMywxNSwxMTMsMjQwLDg5LDE2NywxNzIsMzIsMjIyLDE5OCw0NywxMjcsMTEyLDIyOSw5LDU4LDM0",
"lv": 17878161,
"snd": "XEMQAYBJNX7XZSRSS6CEV6PMGD2Y7BLI46VFQZ2MULRMEGRTQUUS6ZXRFE",
"type": "appl"
}
}
}
```
Read the Smart Contract State
This section is identical to the respective section for AlgoSigner.
For reference, the code to read the smart contract’s counter variable is:
```js
algodClient.getApplicationByID(appIndex).do()
.then((d) => {
const globalState = d['params']['global-state'];
})
.catch((e) => {
console.error(e);
})
```
A sample output is:
```json
[
{
"key": "Y291bnRlcg==",
"value": {
"bytes": "",
"type": 2,
"uint": 12
}
}
]
```
Conclusion
AlgoSigner and WalletConnect are both tools for a website dapp to easily integrate user interaction with a deployed Algorand smart contract. In addition to a developer API to be used on the website, AlgoSigner also provides an Algorand wallet in the form of a browser add-on. These tools eliminate any complexity for non-technical users to use an Algorand application. Lowering the barrier of entry is critical for an Algorand application to achieve mass adoption. In order to garner these benefits, dapp developers need to simply create a website with a user-friendly UI and, under the hood, follow the simple steps and code snippets listed in this tutorial. Integrating both AlgoSigner and WalletConnect into a website dapp is intuitive for the developer and a crucial step for the end user’s overall positive experience.
Credits: Created by Damian Barabonkov, [email protected], GitHub: @DamianB-BitFlipper
Complete Code of Demo Application
This tutorial was developed in parallel with a demo application showcasing both AlgoSigner and WalletConnect. This demo application provides two simple UIs, one which uses AlgoSigner to interact with the “Add One” smart contract and the other using WalletConnect. The full repository is publicly accessible on Github with instructions on how to serve and run the application on your own.
For quick reference, the demo application code is copy-pasted in this tutorial as well. Note: These demo code samples may not be up-to-date with any changes made after this tutorial is published.
AlgoSigner Demo Code
```html
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>AlgoSigner Example DApp</title>
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/[email protected]/css/bulma.min.css">
<link rel="shortcut icon" href="assets/ps-favicon.ico" type="image/x-icon">
<link rel="stylesheet" href="//cdn.jsdelivr.net/gh/highlightjs/[email protected]/build/styles/tomorrow.min.css">
<script src="//cdn.jsdelivr.net/gh/highlightjs/[email protected]/build/highlight.min.js"></script>
<link rel="stylesheet" type="text/css" href="assets/style.css">
</head>
<body>
<nav class="navbar" role="navigation" aria-label="main navigation">
<div class="navbar-menu">
<div class="navbar-start">
<a class="navbar-item"><u>AlgoSigner Example</u></a>
<a class="navbar-item" href="walletconnect.html">WalletConnect Example</a>
</div>
</div>
</nav>
<!-- CONNECT -->
<section class="section">
<div class="container">
<h1 class="title">
Connect
<a class="button is-small is-pulled-right" target="_blank" rel="noopener noreferrer"
href="https://github.com/DamianB-BitFlipper/algosigner-walletconnect-dapp-example/blob/master/algosigner.html#L384">
<span class="icon is-small">
<i class="fab fa-github"></i>
</span>
<span>See code on GitHub</span>
</a>
</h1>
<p class="subtitle">
Connect to AlgoSigner
</p>
<div class="columns">
<div class="column">
<button class="button is-primary is-fullwidth" id="connect">Connect</button>
<pre class="mt-2"><code class="JSON" id="connect-code">response</code></pre>
</div>
<div class="column">
<pre><code class="js"><b>AlgoSigner</b>.connect()
.then((d) => {
...
})
.catch((e) => {
console.error(e);
});</code></pre>
</div>
</div>
</div>
</section>
<!-- /CONNECT -->
<!-- SDKSETUP -->
<section class="section">
<div class="container">
<h1 class="title">
SDK Setup
<a class="button is-small is-pulled-right" target="_blank" rel="noopener noreferrer"
href="https://github.com/DamianB-BitFlipper/algosigner-walletconnect-dapp-example/blob/master/algosigner.html#L400">
<span class="icon is-small">
<i class="fab fa-github"></i>
</span>
<span>See code on GitHub</span>
</a>
</h1>
<p class="subtitle">
Setup a client for algosdk
</p>
<div class="columns">
<div class="column">
<button class="button is-primary is-fullwidth" id="sdksetup">Setup SDK</button>
<pre class="mt-2"><code class="JSON" id="sdk-setup">response</code></pre>
</div>
<div class="column">
<pre><code class="js">const algodServer = 'http://localhost';
const token = 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa';
const port = '4001';
algodClient = new algosdk.Algodv2(token, algodServer, port);
algodClient.healthCheck().do()
.then(d => {
...
})
.catch(e => {
console.error(e);
});</code></pre>
</div>
</div>
</div>
</section>
<!-- /SDKSETUP -->
<!-- ACCOUNTS -->
<section class="section">
<div class="container">
<h1 class="title">
Get TestNet Accounts
<a class="button is-small is-pulled-right" target="_blank" rel="noopener noreferrer"
href="https://github.com/DamianB-BitFlipper/algosigner-walletconnect-dapp-example/blob/master/algosigner.html#L422">
<span class="icon is-small">
<i class="fab fa-github"></i>
</span>
<span>See code on GitHub</span>
</a>
</h1>
<p class="subtitle">
Gets the TestNet accounts in this wallet
</p>
<div class="columns">
<div class="column">
<button class="button is-primary is-fullwidth" id="accounts">Get Accounts</button>
<pre class="mt-2"><code class="JSON" id="accounts-code">response</code></pre>
</div>
<div class="column">
<pre><code class="js"><b>AlgoSigner</b>.accounts({
ledger: 'TestNet'
})
.then((d) => {
accounts = d;
...
})
.catch((e) => {
console.error(e);
});</code></pre>
</div>
</div>
</div>
</section>
<!-- /ACCOUNTS -->
<!-- PARAMS -->
<section class="section">
<div class="container">
<h1 class="title">
Get Params
<a class="button is-small is-pulled-right" target="_blank" rel="noopener noreferrer"
href="https://github.com/DamianB-BitFlipper/algosigner-walletconnect-dapp-example/blob/master/algosigner.html#L451">
<span class="icon is-small">
<i class="fab fa-github"></i>
</span>
<span>See code on GitHub</span>
</a>
</h1>
<p class="subtitle">
Query Algod to get TestNet suggested TX Params
</p>
<div class="columns">
<div class="column">
<button class="button is-primary is-fullwidth" id="params">Get Params</button>
<pre class="mt-2"><code class="JSON" id="params-code">response</code></pre>
</div>
<div class="column">
<pre><code class="js">algodClient.getTransactionParams().do()
.then((d) => {
suggestedTxParams = d;
...
})
.catch((e) => {
console.error(e);
});</code></pre>
</div>
</div>
</div>
</section>
<!-- /PARAMS -->
<!-- SIGN -->
<section class="section">
<div class="container">
<h1 class="title">
Sign Application Call Transaction
<a class="button is-small is-pulled-right" target="_blank" rel="noopener noreferrer"
href="https://github.com/DamianB-BitFlipper/algosigner-walletconnect-dapp-example/blob/master/algosigner.html#L468">
<span class="icon is-small">
<i class="fab fa-github"></i>
</span>
<span>See code on GitHub</span>
</a>
</h1>
<p class="subtitle">
Ask the user to sign a smart contract application call transaction using AlgoSigner's Sign method
</p>
<div class="columns">
<div class="column">
<div class="field">
<div class="control has-icons-left">
<div class="select is-fullwidth">
<select id="from">
<option>Get accounts first</option>
</select>
</div>
<div class="icon is-small is-left">
<i class="fas fa-wallet"></i>
</div>
</div>
</div>
<div class="field">
<p class="control has-icons-left">
<input class="input" id="app-index" placeholder="App Index">
<span class="icon is-small is-left">
<i class="fas fa-chevron-circle-right"></i>
</span>
</p>
</div>
<button class="button is-primary is-fullwidth" id="sign">Sign</button>
<pre class="mt-2"><code id="sign-code">response</code></pre>
</div>
<div class="column">
<pre><code class="js">const txn = algosdk.makeApplicationNoOpTxnFromObject({
from: document.getElementById('from').value,
appIndex: +document.getElementById('app-index').value,
suggestedParams: {...suggestedTxParams}
});
// Use the AlgoSigner encoding library to make the transactions base64
let txn_b64 = <b>AlgoSigner</b>.encoding.msgpackToBase64(txn.toByte());
<b>AlgoSigner</b>.signTxn([{txn: txn_b64}])
.then((d) => {
signedTxs = d;
...
})
.catch((e) => {
console.error(e);
});</code></pre>
</div>
</div>
</div>
</section>
<!-- /SIGN -->
<!-- SEND -->
<section class="section">
<div class="container">
<h1 class="title">
Send Signed Transaction
<a class="button is-small is-pulled-right" target="_blank" rel="noopener noreferrer"
href="https://github.com/DamianB-BitFlipper/algosigner-walletconnect-dapp-example/blob/master/algosigner.html#L496">
<span class="icon is-small">
<i class="fab fa-github"></i>
</span>
<span>See code on GitHub</span>
</a>
</h1>
<p class="subtitle">
Send the previously signed transaction using Algod
</p>
<div class="columns">
<div class="column">
<button class="button is-primary is-fullwidth" id="send">Send</button>
<pre class="mt-2"><code class="JSON" id="send-code">response</code></pre>
</div>
<div class="column">
<pre><code class="js"><b>AlgoSigner</b>.send({
ledger: 'TestNet',
tx: signedTxs[0].blob
})
.then((d) => {
tx = d;
...
})
.catch((e) => {
console.error(e);
});</code></pre>
</div>
</div>
</div>
</section>
<!-- /SEND -->
<!-- PENDING -->
<section class="section">
<div class="container">
<h1 class="title">
Check Pending Transaction
<a class="button is-small is-pulled-right" target="_blank" rel="noopener noreferrer"
href="https://github.com/DamianB-BitFlipper/algosigner-walletconnect-dapp-example/blob/master/algosigner.html#L517">
<span class="icon is-small">
<i class="fab fa-github"></i>
</span>
<span>See code on GitHub</span>
</a>
</h1>
<p class="subtitle">
Query the pending transaction endpoint to check the sent transaction status
</p>
<div class="columns">
<div class="column">
<button class="button is-primary is-fullwidth" id="pending">Check</button>
<pre class="mt-2"><code class="JSON" id="pending-code">response</code></pre>
</div>
<div class="column">
<pre><code class="js">algodClient.pendingTransactionInformation(tx.txId).do()
.then((d) => {
txStatus = d;
...
})
.catch((e) => {
console.error(e);
});</code></pre>
</div>
</div>
</div>
</section>
<!-- /PENDING -->
<!-- READ STATE -->
<section class="section">
<div class="container">
<h1 class="title">
Read State
<a class="button is-small is-pulled-right" target="_blank" rel="noopener noreferrer"
href="https://github.com/DamianB-BitFlipper/algosigner-walletconnect-dapp-example/blob/master/algosigner.html#L533">
<span class="icon is-small">
<i class="fab fa-github"></i>
</span>
<span>See code on GitHub</span>
</a>
</h1>
<p class="subtitle">
Read the global variable state of the smart contract
</p>
<div class="columns">
<div class="column">
<button class="button is-primary is-fullwidth" id="read">Read</button>
<pre class="mt-2"><code class="JSON" id="read-code">response</code></pre>
</div>
<div class="column">
<pre><code class="js">algodClient.getApplicationByID(appIndex).do()
.then((d) => {
globalState = d['params']['global-state'];
...
})
.catch((e) => {
console.error(e);
});</code></pre>
</div>
</div>
</div>
</section>
<!-- /READ STATE -->
<footer class="footer">
<div class="content has-text-centered">
Demo app adapted from <a target="_blank" rel="noopener noreferrer" href="https://purestake.com">
<img src="assets/ps-logo.png" height="28">
</a>
</div>
<div class="content has-text-centered">
By: Damian Barabonkov, damianb[at]mit[dot]edu, <i class="fab fa-github"></i> @DamianB-BitFlipper
</div>
</footer>
<script defer src="https://use.fontawesome.com/releases/v5.3.1/js/all.js"></script>
<script>
hljs.initHighlightingOnLoad();
let suggestedTxParams = {};
let tnAccounts = [];
let signedTxs;
let tx = {};
let algodClient = {};
let appIndex = null;
function connect() {
let connectCodeElem = document.getElementById('connect-code');
AlgoSigner.connect()
.then((d) => {
connectCodeElem.innerHTML = JSON.stringify("Connected Successfully!", null, 2);
})
.catch((e) => {
console.error(e);
connectCodeElem.innerHTML = JSON.stringify(e, null, 2);
})
.finally(() => {
hljs.highlightBlock(connectCodeElem);
});
}
function sdkSetup() {
let sdkSetupCodeElem = document.getElementById('sdk-setup');
const server = 'http://localhost';
const token = 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa';
const port = '4001';
algodClient = new algosdk.Algodv2(token, server, port);
// Health check
algodClient.healthCheck().do()
.then(d => {
sdkSetupCodeElem.innerHTML = JSON.stringify("SDK Initialized!", null, 2);
})
.catch(e => {
sdkSetupCodeElem.innerHTML = JSON.stringify(e, null, 2);
})
.finally(() => {
hljs.highlightBlock(sdkSetupCodeElem);
});
}
function accounts() {
let accountsCodeElem = document.getElementById('accounts-code');
AlgoSigner.accounts({
ledger: 'TestNet'
})
.then((d) => {
accountsCodeElem.innerHTML = JSON.stringify(d, null, 2);
tnAccounts = d;
// Append accounts to account select
let select = document.getElementById('from');
select.textContent = '';
for (var i = d.length - 1; i >= 0; i--) {
let option = document.createElement('option');
option.text = d[i].address;
option.value = d[i].address;
select.appendChild(option);
}
})
.catch((e) => {
console.error(e);
accountsCodeElem.innerHTML = JSON.stringify(e, null, 2);
})
.finally(() => {
hljs.highlightBlock(accountsCodeElem);
});
}
function params() {
let paramsCodeElem = document.getElementById('params-code');
algodClient.getTransactionParams().do()
.then((d) => {
suggestedTxParams = d;
paramsCodeElem.innerHTML = JSON.stringify(suggestedTxParams, null, 2);
})
.catch((e) => {
console.error(e);
paramsCodeElem.innerHTML = JSON.stringify(e, null, 2);
})
.finally(() => {
hljs.highlightBlock(paramsCodeElem);
});
}
function sign() {
let signCodeElem = document.getElementById('sign-code');
appIndex = +document.getElementById('app-index').value;
const txn = algosdk.makeApplicationNoOpTxnFromObject({
from: document.getElementById('from').value,
appIndex: appIndex,
suggestedParams: {...suggestedTxParams}
});
// Use the AlgoSigner encoding library to make the transactions base64
let txn_b64 = AlgoSigner.encoding.msgpackToBase64(txn.toByte());
AlgoSigner.signTxn([{txn: txn_b64}])
.then((d) => {
signedTxs = d;
signCodeElem.innerHTML = JSON.stringify(d, null, 2);
})
.catch((e) => {
console.error(e);
signCodeElem.innerHTML = JSON.stringify(e, null, 2);
})
.finally(() => {
hljs.highlightBlock(signCodeElem);
});
}
function send() {
let sendCodeElem = document.getElementById('send-code');
// Sending first transaction only for demo purposes
AlgoSigner.send({
ledger: 'TestNet',
tx: signedTxs[0].blob
})
.then((d) => {
sendCodeElem.innerHTML = JSON.stringify(d, null, 2);
tx = d;
})
.catch((e) => {
console.error(e);
sendCodeElem.innerHTML = JSON.stringify(e, null, 2);
})
.finally(() => {
hljs.highlightBlock(sendCodeElem);
});
}
function pending() {
let pendingCodeElem = document.getElementById('pending-code');
algodClient.pendingTransactionInformation(tx.txId).do()
.then((d) => {
pendingCodeElem.innerHTML = JSON.stringify(d, toJsonReplace, 2);
})
.catch((e) => {
console.error(e);
pendingCodeElem.innerHTML = JSON.stringify(e, null, 2);
})
.finally(() => {
hljs.highlightBlock(pendingCodeElem);
});
}
function read() {
let readCodeElem = document.getElementById('read-code');
algodClient.getApplicationByID(appIndex).do()
.then((d) => {
const globalState = d['params']['global-state'];
readCodeElem.innerHTML = JSON.stringify(globalState, null, 2);
})
.catch((e) => {
console.error(e);
readCodeElem.innerHTML = JSON.stringify(e, null, 2);
})
.finally(() => {
hljs.highlightBlock(readCodeElem);
});
}
document.getElementById('connect').addEventListener('click', connect);
document.getElementById('sdksetup').addEventListener('click', sdkSetup);
document.getElementById('accounts').addEventListener('click', accounts);
document.getElementById('params').addEventListener('click', params);
document.getElementById('sign').addEventListener('click', sign);
document.getElementById('send').addEventListener('click', send);
document.getElementById('pending').addEventListener('click', pending);
document.getElementById('read').addEventListener('click', read);
</script>
<script src="https://unpkg.com/algosdk@latest"></script>
<script src="./tx-test/common/helper.js"></script>
</body>
</html>
```
WalletConnect Demo Code
```html
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>WalletConnect Example DApp</title>
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/[email protected]/css/bulma.min.css">
<link rel="shortcut icon" href="assets/wc-favicon.ico" type="image/x-icon">
<link rel="stylesheet" href="//cdn.jsdelivr.net/gh/highlightjs/[email protected]/build/styles/tomorrow.min.css">
<script src="//cdn.jsdelivr.net/gh/highlightjs/[email protected]/build/highlight.min.js"></script>
<link rel="stylesheet" type="text/css" href="assets/style.css">
</head>
<body>
<nav class="navbar" role="navigation" aria-label="main navigation">
<div class="navbar-menu">
<div class="navbar-start">
<a class="navbar-item" href="algosigner.html">AlgoSigner Example</a>
<a class="navbar-item"><u>WalletConnect Example</u></a>
</div>
</div>
</nav>
<!-- CONNECT -->
<section class="section">
<div class="container">
<h1 class="title">
Connect
<a class="button is-small is-pulled-right" target="_blank" rel="noopener noreferrer"
href="https://github.com/DamianB-BitFlipper/algosigner-walletconnect-dapp-example/blob/master/walletconnect.html#L1">
<span class="icon is-small">
<i class="fab fa-github"></i>
</span>
<span>See code on GitHub</span>
</a>
</h1>
<p class="subtitle">
Connect to the Algorand smartphone wallet using WalletConnect
</p>
<div class="columns">
<div class="column">
<button class="button is-primary is-fullwidth" id="connect">Connect</button>
<pre class="mt-2"><code class="JSON" id="connect-code">response</code></pre>
</div>
<div class="column">
<pre><code class="js">// Create a walletConnector
<b>walletConnector</b> = new <b>WalletConnect</b>.default({
bridge: "https://bridge.walletconnect.org", // Required
qrcodeModal: WalletConnectQRCodeModal.default,
});
// Check if this is a new connection
if (!<b>walletConnector</b>.connected) {
// Create new session
<b>walletConnector</b>.createSession();
...
}</code></pre>
</div>
</div>
</div>
</section>
<!-- /CONNECT -->
<!-- SDKSETUP -->
<section class="section">
<div class="container">
<h1 class="title">
SDK Setup
<a class="button is-small is-pulled-right" target="_blank" rel="noopener noreferrer"
href="https://github.com/DamianB-BitFlipper/algosigner-walletconnect-dapp-example/blob/master/walletconnect.html#L1">
<span class="icon is-small">
<i class="fab fa-github"></i>
</span>
<span>See code on GitHub</span>
</a>
</h1>
<p class="subtitle">
Setup client for algosdk
</p>
<div class="columns">
<div class="column">
<button class="button is-primary is-fullwidth" id="sdksetup">Setup SDK</button>
<pre class="mt-2"><code class="JSON" id="sdk-setup">response</code></pre>
</div>
<div class="column">
<pre><code class="js">const algodServer = 'http://localhost';
const token = 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa';
const port = '4001';
algodClient = new algosdk.Algodv2(token, algodServer, port);
algodClient.healthCheck().do()
.then(d => {
...
})
.catch(e => {
console.error(e);
});</code></pre>
</div>
</div>
</div>
</section>
<!-- /SDKSETUP -->
<!-- ACCOUNTS -->
<section class="section">
<div class="container">
<h1 class="title">
Get TestNet Accounts
<a class="button is-small is-pulled-right" target="_blank" rel="noopener noreferrer"
href="https://github.com/PureStake/algosigner-walletconnect-dapp-example/blob/master/payment.html#L336">
<span class="icon is-small">
<i class="fab fa-github"></i>
</span>
<span>See code on GitHub</span>
</a>
</h1>
<p class="subtitle">
Gets the TestNet accounts in this wallet
</p>
<div class="columns">
<div class="column">
<button class="button is-primary is-fullwidth" id="accounts">Get Accounts</button>
<pre class="mt-2"><code class="JSON" id="accounts-code">response</code></pre>
</div>
<div class="column">
<pre><code class="js">if (<b>walletConnector</b>.connected) {
const { accounts } = <b>walletConnector</b>;
...
}</code></pre>
</div>
</div>
</div>
</section>
<!-- /ACCOUNTS -->
<!-- PARAMS -->
<section class="section">
<div class="container">
<h1 class="title">
Get Params
<a class="button is-small is-pulled-right" target="_blank" rel="noopener noreferrer"
href="https://github.com/PureStake/algosigner-walletconnect-dapp-example/blob/master/payment.html#L365">
<span class="icon is-small">
<i class="fab fa-github"></i>
</span>
<span>See code on GitHub</span>
</a>
</h1>
<p class="subtitle">
Query Algod to get TestNet suggested TX Params
</p>
<div class="columns">
<div class="column">
<button class="button is-primary is-fullwidth" id="params">Get Params</button>
<pre class="mt-2"><code class="JSON" id="params-code">response</code></pre>
</div>
<div class="column">
<pre><code class="js">algodClient.getTransactionParams().do()
.then((d) => {
suggestedTxParams = d;
...
})
.catch((e) => {
console.error(e);
});</code></pre>
</div>
</div>
</div>
</section>
<!-- /PARAMS -->
<!-- SIGN -->
<section class="section">
<div class="container">
<h1 class="title">
Sign Application Call Transaction
<a class="button is-small is-pulled-right" target="_blank" rel="noopener noreferrer"
href="https://github.com/PureStake/algosigner-walletconnect-dapp-example/blob/master/payment.html#L385">
<span class="icon is-small">
<i class="fab fa-github"></i>
</span>
<span>See code on GitHub</span>
</a>
</h1>
<p class="subtitle">
Ask the user to sign a smart contract application call transaction using the Algorand wallet
</p>
<div class="columns">
<div class="column">
<div class="field">
<div class="control has-icons-left">
<div class="select is-fullwidth">
<select id="from">
<option>Get accounts first</option>
</select>
</div>
<div class="icon is-small is-left">
<i class="fas fa-wallet"></i>
</div>
</div>
</div>
<div class="field">
<p class="control has-icons-left">
<input class="input" id="app-index" placeholder="App Index">
<span class="icon is-small is-left">
<i class="fas fa-chevron-circle-right"></i>
</span>
</p>
</div>
<button class="button is-primary is-fullwidth" id="sign">Sign</button>
<pre class="mt-2"><code id="sign-code">response</code></pre>
</div>
<div class="column">
<pre><code class="js">const txn = algosdk.makeApplicationNoOpTxnFromObject({
from: document.getElementById('from').value,
appIndex: appIndex,
suggestedParams: {...suggestedTxParams}
});
txns_b64 = [
{
txn: arrayBufferToBase64(algosdk.encodeUnsignedTransaction(txn)),
message: 'Invoke smart contract call to add one', // Description of transaction to be signed
},
];
const requestParams = [txns_b64];
const request = JsonRpcUtils.formatJsonRpcRequest("algo_signTxn", requestParams);
signedTxns = await <b>walletConnector</b>.sendCustomRequest(request);</code></pre>
</div>
</div>
</div>
</section>
<!-- /SIGN -->
<!-- SEND -->
<section class="section">
<div class="container">
<h1 class="title">
Send Signed Transaction
<a class="button is-small is-pulled-right" target="_blank" rel="noopener noreferrer"
href="https://github.com/PureStake/algosigner-walletconnect-dapp-example/blob/master/payment.html#L413">
<span class="icon is-small">
<i class="fab fa-github"></i>
</span>
<span>See code on GitHub</span>
</a>
</h1>
<p class="subtitle">
Send the previously signed transaction using AlgoSigner's Send method
</p>
<div class="columns">
<div class="column">
<button class="button is-primary is-fullwidth" id="send">Send</button>
<pre class="mt-2"><code class="JSON" id="send-code">response</code></pre>
</div>
<div class="column">
<pre><code class="js">algodClient.sendRawTransaction(new Uint8Array(signedTxns[0])).do()
.then((d) => {
tx = d;
...
})
.catch((e) => {
console.error(e);
});</code></pre>
</div>
</div>
</div>
</section>
<!-- /SEND -->
<!-- PENDING -->
<section class="section">
<div class="container">
<h1 class="title">
Check pending transaction
<a class="button is-small is-pulled-right" target="_blank" rel="noopener noreferrer"
href="https://github.com/PureStake/algosigner-walletconnect-dapp-example/blob/master/payment.html#L433">
<span class="icon is-small">
<i class="fab fa-github"></i>
</span>
<span>See code on GitHub</span>
</a>
</h1>
<p class="subtitle">
Query the pending transaction endpoint to check the sent transaction status
</p>
<div class="columns">
<div class="column">
<button class="button is-primary is-fullwidth" id="pending">Check</button>
<pre class="mt-2"><code class="JSON" id="pending-code">response</code></pre>
</div>
<div class="column">
<pre><code class="js">algodClient.pendingTransactionInformation(tx.txId).do()
.then((d) => {
txStatus = d;
...
})
.catch((e) => {
console.error(e);
});</code></pre>
</div>
</div>
</div>
</section>
<!-- /PENDING -->
<!-- READ STATE -->
<section class="section">
<div class="container">
<h1 class="title">
Read State
<a class="button is-small is-pulled-right" target="_blank" rel="noopener noreferrer"
href="https://github.com/PureStake/algosigner-walletconnect-dapp-example/blob/master/payment.html#L320">
<span class="icon is-small">
<i class="fab fa-github"></i>
</span>
<span>See code on GitHub</span>
</a>
</h1>
<p class="subtitle">
Read the global variable state of the smart contract
</p>
<div class="columns">
<div class="column">
<button class="button is-primary is-fullwidth" id="read">Read</button>
<pre class="mt-2"><code class="JSON" id="read-code">response</code></pre>
</div>
<div class="column">
<pre><code class="js">algodClient.getApplicationByID(appIndex).do()
.then((d) => {
globalState = d['params']['global-state'];
...
})
.catch((e) => {
console.error(e);
});</code></pre>
</div>
</div>
</div>
</section>
<!-- /READ STATE -->
<!-- DISCONNECT -->
<section class="section">
<div class="container">
<h1 class="title">
Disconnect
<a class="button is-small is-pulled-right" target="_blank" rel="noopener noreferrer"
href="https://github.com/PureStake/algosigner-walletconnect-dapp-example/blob/master/payment.html#L320">
<span class="icon is-small">
<i class="fab fa-github"></i>
</span>
<span>See code on GitHub</span>
</a>
</h1>
<p class="subtitle">
Terminate the current WalletConnect session
</p>
<div class="columns">
<div class="column">
<button class="button is-primary is-fullwidth" id="disconnect">Disconnect</button>
<pre class="mt-2"><code class="JSON" id="disconnect-code">response</code></pre>
</div>
<div class="column">
<pre><code class="js">// Kill the current `walletConnector` session
<b>walletConnector</b>.killSession();</code></pre>
</div>
</div>
</div>
</section>
<!-- /DISCONNECT -->
<footer class="footer">
<div class="content has-text-centered">
Demo app adapted from <a target="_blank" rel="noopener noreferrer" href="https://purestake.com">
<img src="assets/ps-logo.png" height="28">
</a>
</div>
<div class="content has-text-centered">
By: Damian Barabonkov, damianb[at]mit[dot]edu, <i class="fab fa-github"></i> @DamianB-BitFlipper
</div>
</footer>
<script defer src="https://use.fontawesome.com/releases/v5.3.1/js/all.js"></script>
<script>
hljs.initHighlightingOnLoad();
let suggestedTxParams = {};
let tnAccounts = [];
let tx = {};
let signedTxns = [];
let algodClient = {};
let walletConnector = null;
let appIndex = null;
function connect() {
let connectCodeElem = document.getElementById('connect-code');
// Create a `walletConnector`
walletConnector = new WalletConnect.default({
bridge: "https://bridge.walletconnect.org", // Required
qrcodeModal: WalletConnectQRCodeModal.default,
});
// Check if this is a new connection
if (!walletConnector.connected) {
// Create new session
walletConnector.createSession();
connectCodeElem.innerHTML = JSON.stringify("Initiating connection to Algorand wallet", null, 2);
} else {
connectCodeElem.innerHTML = JSON.stringify("Already connected to Algorand wallet", null, 2);
}
hljs.highlightBlock(connectCodeElem);
//
// All of the event handlers
//
// Subscribe to connection events
walletConnector.on("connect", (error, payload) => {
if (error) {
console.error(error);
connectCodeElem.innerHTML = JSON.stringify(error, null, 2);
hljs.highlightBlock(connectCodeElem);
return;
}
// Get provided accounts
const { accounts } = payload.params[0];
connectCodeElem.innerHTML = JSON.stringify("Connected to Algorand wallet!", null, 2);
hljs.highlightBlock(connectCodeElem);
});
walletConnector.on("session_update", (error, payload) => {
if (error) {
console.error(error);
connectCodeElem.innerHTML = JSON.stringify(error, null, 2);
hljs.highlightBlock(connectCodeElem);
return;
}
// Get updated accounts
const { accounts } = payload.params[0];
});
walletConnector.on("disconnect", (error, payload) => {
if (error) {
console.error(error);
connectCodeElem.innerHTML = JSON.stringify(error, null, 2);
hljs.highlightBlock(connectCodeElem);
return;
}
connectCodeElem.innerHTML = JSON.stringify("Disconnected from Algorand wallet!", null, 2);
hljs.highlightBlock(connectCodeElem);
});
}
function sdkSetup() {
let sdkSetupCodeElem = document.getElementById('sdk-setup');
const server = 'http://localhost';
const token = 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa';
const port = '4001';
algodClient = new algosdk.Algodv2(token, server, port);
// Health check
algodClient.healthCheck().do()
.then(d => {
sdkSetupCodeElem.innerHTML = JSON.stringify("SDK Initialized!", null, 2);
})
.catch(e => {
sdkSetupCodeElem.innerHTML = JSON.stringify(e, null, 2);
})
.finally(() => {
hljs.highlightBlock(sdkSetupCodeElem);
});
}
function accounts() {
let accountsCodeElem = document.getElementById('accounts-code');
if (walletConnector.connected) {
const { accounts } = walletConnector;
accountsCodeElem.innerHTML = JSON.stringify(accounts, null, 2);
// Append accounts to account select
let select = document.getElementById('from');
select.textContent = '';
for (var i = accounts.length - 1; i >= 0; i--) {
let option = document.createElement('option');
option.text = accounts[i];
option.value = accounts[i];
select.appendChild(option);
}
}
hljs.highlightBlock(accountsCodeElem);
}
function params() {
let paramsCodeElem = document.getElementById('params-code');
algodClient.getTransactionParams().do()
.then((d) => {
suggestedTxParams = d;
paramsCodeElem.innerHTML = JSON.stringify(suggestedTxParams, null, 2);
})
.catch((e) => {
console.error(e);
paramsCodeElem.innerHTML = JSON.stringify(e, null, 2);
})
.finally(() => {
hljs.highlightBlock(paramsCodeElem);
});
}
async function sign() {
let signCodeElem = document.getElementById('sign-code');
appIndex = +document.getElementById('app-index').value;
const txn = algosdk.makeApplicationNoOpTxnFromObject({
from: document.getElementById('from').value,
appIndex: appIndex,
suggestedParams: {...suggestedTxParams}
});
txns_b64 = [
{
txn: arrayBufferToBase64(algosdk.encodeUnsignedTransaction(txn)),
message: 'Invoke smart contract call to add one', // Description of transaction to be signed
// Note: if the transaction does not need to be signed (because it's part of an atomic group
// that will be signed by another party), specify an empty singers array like so:
// signers: [],
},
];
const requestParams = [txns_b64];
const request = JsonRpcUtils.formatJsonRpcRequest("algo_signTxn", requestParams);
signedTxns = await walletConnector.sendCustomRequest(request);
signCodeElem.innerHTML = JSON.stringify(signedTxns, toJsonReplace, 2);
hljs.highlightBlock(signCodeElem);
}
function send() {
let sendCodeElem = document.getElementById('send-code');
algodClient.sendRawTransaction(new Uint8Array(signedTxns[0])).do()
.then((d) => {
sendCodeElem.innerHTML = JSON.stringify(d, null, 2);
tx = d;
})
.catch((e) => {
console.error(e);
sendCodeElem.innerHTML = JSON.stringify(e, null, 2);
})
.finally(() => {
hljs.highlightBlock(sendCodeElem);
});
}
function pending() {
let pendingCodeElem = document.getElementById('pending-code');
algodClient.pendingTransactionInformation(tx.txId).do()
.then((d) => {
pendingCodeElem.innerHTML = JSON.stringify(d, toJsonReplace, 2);
})
.catch((e) => {
console.error(e);
pendingCodeElem.innerHTML = JSON.stringify(e, null, 2);
})
.finally(() => {
hljs.highlightBlock(pendingCodeElem);
});
}
function read() {
let readCodeElem = document.getElementById('read-code');
algodClient.getApplicationByID(appIndex).do()
.then((d) => {
const globalState = d['params']['global-state'];
readCodeElem.innerHTML = JSON.stringify(globalState, null, 2);
})
.catch((e) => {
console.error(e);
readCodeElem.innerHTML = JSON.stringify(e, null, 2);
})
.finally(() => {
hljs.highlightBlock(readCodeElem);
});
}
function disconnect() {
let disconnectCodeElem = document.getElementById('disconnect-code');
// Kill the current `walletConnector` session
walletConnector.killSession();
disconnectCodeElem.innerHTML = JSON.stringify("Session Terminated", null, 2);
hljs.highlightBlock(disconnectCodeElem);
}
document.getElementById('connect').addEventListener('click', connect);
document.getElementById('sdksetup').addEventListener('click', sdkSetup);
document.getElementById('accounts').addEventListener('click', accounts);
document.getElementById('params').addEventListener('click', params);
document.getElementById('sign').addEventListener('click', sign);
document.getElementById('send').addEventListener('click', send);
document.getElementById('pending').addEventListener('click', pending);
document.getElementById('read').addEventListener('click', read);
document.getElementById('disconnect').addEventListener('click', disconnect);
</script>
<script src="https://unpkg.com/algosdk@latest"></script>
<script src="https://unpkg.com/@walletconnect/client@latest"></script>
<script src="https://unpkg.com/algorand-walletconnect-qrcode-modal@latest"></script>
<script src="https://unpkg.com/@json-rpc-tools/utils@latest"></script>
<script src="./tx-test/common/helper.js"></script>
</body>
</html>
```
Disclaimer
This solution is intended for learning purposes only. It does not cover error checking and other edge cases therefore, should not be used as a production application.
Credits: Created by Damian Barabonkov, [email protected], GitHub: @DamianB-BitFlipper
Disclaimer — This is a sponsored article. DappRadar does not endorse any content or product on this page. DappRadar aims to provide accurate information, but readers should always do their own research before taking action. Articles by DappRadar can not be considered as investment advice.