AlphaWallet / Web3E

Web3E Ethereum for Embedded devices running Arduino framework
MIT License
142 stars 45 forks source link
dapp esp32-arduino ethereum tokenscript web3

Web3E Ethereum for Embedded devices

PlatformIO Registry

What's New: v1.44:

v1.43:

v1.42:

v1.41:

v1.4:

v1.34:

v1.33:

For a working demonstration of TokenScript and Web3E please try minting an ERC5169/TokenScript powered ERC721 NFT here: http://smarttokenlabs.duckdns.org You will need AlphaWallet on your mobile phone; simply replace the main.cpp in the project here: https://github.com/TokenScript/TokenScriptTestContracts/tree/master/firmware with the generated project which the Token wizard generates.

Web3E is a fully functional Web3 framework for Embedded devices running Arduino. Web3E now has methods which allow you to use TokenScript in your IoT solution for rapid deployment. Tested mainly on ESP32 and working on ESP8266. Also included is a rapid development DApp injector to convert your embedded server into a fully integrated Ethereum DApp.

Starting from a simple requirement - write a DApp capable of running on an ESP32 which can serve as a security door entry system. Some brave attempts can be found in scattered repos but ultimately even the best are just dapp veneers or have ingenous and clunky hand-rolled communication systems like the Arduino wallet attempts.

What is required is a method to write simple, fully embedded DApps which give you a zero infrastructure and total security solution. It is possible that as Ethereum runs natively on embedded devices a new revolution in the blockchain saga will begin. Now you have the ability to write a fully embedded DApp that gives you the security and flexibility of Ethereum in an embedded device.

New Features

Features

Installation

[env:esp32dev]
platform = espressif32
board = esp32dev
framework = arduino

; Serial Monitor options
monitor_speed = 115200

lib_ldf_mode = deep
lib_deps =
  # Using a library name
  Web3E

Please note: you may need to use 'lib_ldf_mode = deep' to locate some ESP32 libraries like 'EEPROM.h' also: if you use many other libraries in your application you may need to add 'board_build.partitions = no_ota.csv' to fit your firmware

Example TokenScript flow

The advantage of using TokenScript rather than a dapp is evident from looking at the code example. You will have a very nice user interface defined with html/css with the calling code written in small JavaScript functions. The user would quickly find the entry token in their wallet.

New in Version 1.3 - easy node setup

Web3E now has a series of API key free node endpoints for many EVMs. The full list can be found in src/chainIds.h. Supply one of these to the Web3 object in your main sketch to start connecting. eg:

Web3 *web3 = new Web3(SEPOLIA_ID);
Web3 *web3 = new Web3(MAINNET_ID);
Web3 *web3 = new Web3(MUMBAI_TEST_ID);

Note, if you have an Infura API key and wish to use Infura where possible, edit Web3.h like this:

#define USING_INFURA 1
#define INFURA_KEY "1234567890abcdef1234567890abcdef" //<--- your Infura key here

Web3E will adjust the node endpoint to use Infura for all Infura supported networks. Note that you will need to enable Infura in the dashboard for all these networks; otherwise use the free connections.

Example Web3E DApp flow

AlphaWallet Security Door

https://github.com/alpha-wallet/Web3E-Application

Full source code for the system active at the AlphaWallet office. To get it working you need:

Included in the package are seven samples

The push transaction sample requires a little work to get running. You have to have an Ethereum wallet, some testnet ETH, the private key for that testnet eth, and then create some ERC20 and ERC875 tokens in the account.

Usage

See Wallet Bridge 1, 2 and 3 examples for complete source

Standard: Using ScriptProxy Bridge

This style of connection will work in almost every situation, from inside or outside of the WiFi connection

In this usage pattern, your IoT device will connect to a proxy server which provides a bridge to the TokenScript running on your wallet. The source code for the proxy server can be found here: Script Proxy

Declare TcpBridge, KeyID and Web3 in globals:

TcpBridge *tcpConnection;
Web3 *web3;
KeyID *keyID;

Setup your Web node

web3 = new Web3(MAINNET_ID);

Start UDP bridge after connecting to WiFi:

    tcpConnection = new TcpBridge();
    tcpConnection->setKey(keyID, web3);
    tcpConnection->startConnection();

Within your loop() check for API call:

    tcpConnection->checkClientAPI(&handleTCPAPI);

Handle API call in the callback:

enum APIRoutes
{
  api_unknown,
  api_getChallenge,
  api_checkSignature,
  api_checkSignatureLock,
  api_checkMarqueeSig,
  api_end
};

std::map<std::string, APIRoutes> s_apiRoutes;

void Initialize()
{
  s_apiRoutes["getChallenge"] = api_getChallenge;
  s_apiRoutes["checkSignature"] = api_checkSignature;
  s_apiRoutes["checkSignatureLock"] = api_checkSignatureLock;
  s_apiRoutes["end"] = api_end;
}

std::string handleTCPAPI(APIReturn* apiReturn)
{
    switch (s_apiRoutes[apiReturn->apiName])
    {
    case api_getChallenge:
        Serial.println(currentChallenge.c_str());
        udpBridge->sendResponse(currentChallenge, methodId);
        break;
    case api_checkSignature:
        {
            //EC-Recover address from signature and challenge
            string address = Crypto::ECRecoverFromPersonalMessage(&apiReturn->params["sig"], &currentChallenge);  
            //Check if this address has our entry token
            boolean hasToken = QueryBalance(&address);
            updateChallenge(); //generate a new challenge after each check
            if (hasToken)
            {
                udpBridge->sendResponse("pass", methodId);
                OpenDoor(); //Call your code that opens a door or performs the required 'pass' action
            }
            else
            {
                udpBridge->sendResponse("fail: doesn't have token", methodId);
            }
        }
                break;  

Advanced: Direct TCP connection

Use this if you have admin control of your WiFi Router, as you need to set up port forwarding to access the unit from outside your WiFi

In this usage pattern, the TokenScript running on the wallet will connect directly to the IoT device. Notice that this means your IoT is directly accessible to the internet, which may be susceptible to exploit.

Ethereum transaction (ie send ETH to address):

// Setup Web3 and Contract with Private Key
...
web3 = new Web3(RINKEBY_ID);
Contract contract(web3, "");
contract.SetPrivateKey(PRIVATE_KEY);
uint32_t nonceVal = (uint32_t)web3->EthGetTransactionCount(&address); //obtain the next nonce
uint256_t weiValue = Util::ConvertToWei(0.25, 18); //send 0.25 eth
unsigned long long gasPriceVal = 1000000000ULL;
uint32_t  gasLimitVal = 90000;
string emptyString = "";
string toAddress = "0xC067A53c91258ba513059919E03B81CF93f57Ac7";
string result = contract.SendTransaction(nonceVal, gasPriceVal, gasLimitVal, &toAddress, &weiValue, &emptyString);

Query ETH balance:

uint256_t balance = web3->EthGetBalance(&address); //obtain balance in Wei
string balanceStr = Util::ConvertWeiToEthString(&balance, 18); //get string balance as Eth (18 decimals)

Query ERC20 Balance:

string address = string("0x007bee82bdd9e866b2bd114780a47f2261c684e3");
Contract contract(web3, "0x20fe562d797a42dcb3399062ae9546cd06f63280"); //contract is on Ropsten

//Obtain decimals to correctly display ERC20 balance (if you already know this you can skip this step)
string param = contract.SetupContractData("decimals()", &address);
string result = contract.ViewCall(&param);
int decimals = web3->getInt(&result);

//Fetch the balance in base units
param = contract.SetupContractData("balanceOf(address)", &address);
result = contract.ViewCall(&param);

uint256_t baseBalance = web3->getUint256(&result);
string balanceStr = Util::ConvertWeiToEthString(&baseBalance, decimals); //convert balance to double style using decimal places

Send ERC20 Token:

string contractAddr = "0x20fe562d797a42dcb3399062ae9546cd06f63280";
Contract contract(web3, contractAddr.c_str());
contract.SetPrivateKey(<Your private key>);

//Get contract name
string param = contract.SetupContractData("name()", &addr);
string result = contract.ViewCall(&param);
string interpreted = Util::InterpretStringResult(web3->getString(&result).c_str());
Serial.println(interpreted.c_str());

//Get Contract decimals
param = contract.SetupContractData("decimals()", &addr);
result = contract.ViewCall(&param);
int decimals = web3->getInt(&result);
Serial.println(decimals);

unsigned long long gasPriceVal = 22000000000ULL;
uint32_t  gasLimitVal = 4300000;

//amount of erc20 token to send, note we use decimal value obtained earlier
uint256_t weiValue = Util::ConvertToWei(0.1, decimals);

//get nonce
uint32_t nonceVal = (uint32_t)web3->EthGetTransactionCount(&addr);
string toAddress = "0x007bee82bdd9e866b2bd114780a47f2261c684e3";
string valueStr = "0x00";

//Setup contract function call
string p = contract.SetupContractData("transfer(address,uint256)", &toAddress, &weiValue); //ERC20 function plus params

//push transaction to ethereum
result = contract.SendTransaction(nonceVal, gasPriceVal, gasLimitVal, &contractAddr, &valueStr, &p);
string transactionHash = web3->getString(&result);

Originally forked https://github.com/kopanitsa/web3-arduino but with almost a complete re-write it is a new framework entirely.

Libraries used:

Coming soon:

Donations

If you support the cause, we could certainly use donations to help fund development:

0xbc8dAfeacA658Ae0857C80D8Aa6dE4D487577c63