No Eth? No Wallet?! No Problem! How to Make Your Dapp Work Gasless for Those Poor Crypto Muggles

Dror Tirosh
TabooKey
Published in
10 min readFeb 15, 2019

In this article, we explain how to upgrade a dApp to work with any browser, without even needing to setup a wallet first. This means users will be able to interact with your dApp without having to install anything or pass KYC/AML just to buy ETH to pay for gas fees.

TabooKey’s open source Gas Stations Network project (EIP 1613), described in our previous 1–800 Ethereum post by Yoav Weiss, is a no-strings-attached gift to the Ethereum community that helps dapps acquire new users by making themselves accessible to users that either don’t have a wallet at all, or don’t have any ETH in it, which includes pretty much all puny earthlings (aside from the rounding error of early crypto adopters).

Usually to interact with an Ethereum smart contract, users need a wallet that has ETH in it to pay for transaction gas fees. With a few simple changes smart contracts can use the TabooKey Gas Station Network to add the equivalent of a 1–800 toll free number that users with no gas can interact with. For some use cases, users won’t even need to create a wallet! This can be implemented in a carefully controlled way that minimizes the risk of abuse.

Ethereum does not support this directly, so we need some help, from the on-chain RelayHub contract, and the off-chain network of RelayServers.

There are 4 steps to setting up your dapp to support gasless transactions:

  1. Decide on your access model: Since you (the contract owner) pay for the gas, and the clients access it for free, you should decide which clients and which calls are allowed to access the contract.
    In our example, we simply allow any client, but we will also show how you can charge your clients by other means (e.g. charge them with tokens!).
  2. Modify the contract to accept relayed calls:
    Note that the contract always accepts “normal” call. We only add a mechanism to know who the relay caller is, when the call comes through a relay.
  3. Modify your client: make your web application (or mobile app) go through the relay to make calls.
  4. Fill up your contract’s “gas tank” for the first time. Since the contract pays for the calls, you (as the contract owner) have to fund it periodically so it can pay the relay-servers for their service.

We already deployed a sample “MetaCoin” project which uses this gasless model. You are more than welcome to experiment here.

Now that we’ve talked about the concept of what we’re doing, let’s dive into the topic and understand how to convert it from a “normal” dapp into a gasless dapp.

Some notes about the sample:

  • We added a button “Mint” so that any user can have initial tokens to play with.
  • The sample shows it can be used with Metamask (for signing only!) or without it, with an ephemeral key saved in a cookie.
    Note that the Metamask address is different from the one without Metamask. You can experiment and move coins between them.

Dapp Access Model

Since you (the contract owner) pay for the gas on behalf of your clients, you should decide which callers you wish to pay for.

The actual calls to your dapp are created by a relay, but even a relay can’t force you to pay for a call. If your contract decides not to accept a call (by your code in accept_relayed_call() as we’ll see later), then, even if the relay will put that transaction on the blockchain, not only it will get rejected, the relay will pay for that rejection. You’re covered.

Possible options as follows:

  • Free - let every client make a call (after all, you’re also building the webapp, right?). That approach is great for a sample app (as we do here), but in the real world, Eve, a malicious actor might take your webapp code, modify it to make repeated calls and exhaust your funds. Eve might not gain anything — but Alice and Bob, your legitimate users, will be blocked from accessing your service.
  • Known users - allow Alice and Bob, your known users, to access your contract, and block anyone else. That is a great approach since if Bob attempts to abuse the contract, you can block him.
    But there is a chicken-and-egg problem: how do you know your users? Alice and Bob have to log in the first time, so you can know them.
  • Paying by another method - Imagine you can have a service that pays directly in your tokens. Well, you can do that with a gasless client: Your contract, JoeTokenContract, accepts the call from Alice, verifies that Alice has enough JoeTokens, and lets her pay you back, for the transaction with her JoeTokens.
    You (the contract owner) still pay the relays with ether gas, but Alice has paid you back with JoeTokens.
  • Combination - to solve the known-users problem, you might want to accept “create-account” calls from new users, and after that limit calls to known users only. Alice, a new user, signs into the app, maybe solving a captcha to prove that she’s human, and is allowed to call “create-account” once. After registration, Alice is a known user and can use the rest of the contract, just as long as she doesn’t abuse that trust and gets herself banned. It is possible to create such complex rules, but we won’t describe them here.

Prerequisites

This tutorial assumes that you perform all actions inside a Truffle project of your dapp. You must have npm installed. If you’re using linux, make sure you have npm version > 8 (more specifically, make sure that npx is available).

In order to run a relay locally, our project uses a docker container, so make sure you also have docker installed. Instead, if you run the sample against “ropsten” testnet, then you can use the public relay network, and docker is not required.

Install Sample Contract

In the following section, we will modify the sample meta-coin, and add relay support to it.

  • First, download the sample MetaCoin project (note that you need to run ganache-cli in another terminal window before you can run “install”):
git clone https://github.com/tabookey/metacoin.git
cd metacoin
npm install
  • We added a “migrate” script named 3_fund_metamask.jsto help fund the Metamask account in the local environment. Just update your Metamask address account in this script, and you’ll have ether to work with each time you restart ganache.
  • You can test-run it (before we add our relay support to it) by starting “ganache” in another window, and running:
npx truffle migrate
npm run dev
  • Notice this sample has an extra “mint” button: each account is allowed to mint itself 10000 coins.

Adding “gasless” Support to our Project

First, include “tabookey-gasless” client lib in your project:

npm install tabookey-gasless

Now, we need to start our ganache node and make sure we have a RelayHub contract deployed on it, and also a running relay server. So stop ganache (if it is running) and run (in another window):

npm explore tabookey-gasless npm run gsn-dock-relay-ganache

This command runs a docker image, so the first run will take some time to download and start. It will start ganache, deploy a RelayHub on it, and then start a relay server.

It dumps a lot of logs. Look for “Done Registering” log message. The server continues to dump logs every minute, so the last line says: “Relay registered lately. No need to reregister”. The relay is now ready to accept calls from a client.

Contract Modifications

In this step, we will modify our contract (contract/MetaCoin.sol) so it will accept relay calls.

The first thing a contract has to do is to inherit from RelayRecipient contract:

import “tabookey-gasless/contracts/RelayRecipient.sol”;contract MetaCoin is RelayRecipient

Now, you need to implement the methods to let the RelayHub know which requests you accept. For return values, all integers bigger than zero are error codes (note: 1, 2 are reserved by RelayHub), while returning zero means that your contract will accept the call. The implementation below simply accepts all calls. Later on, we’ll add other strategies.

function accept_relayed_call(address /*relay*/, address /*from*/,
bytes /*encoded_function*/, uint /*gas_price*/,
uint /*transaction_fee*/ ) public view returns(uint32) {
return 0; // accept everything.
}
// nothing to be done post-call.
// still, we must implement this method.
function post_relayed_call(address /*relay*/, address /*from*/,
bytes /*encoded_function*/, bool /*success*/,
uint /*used_gas*/, uint /*transaction_fee*/ ) public {
}

Next, you must specify the RelayHub you accept requests from. We add a method to set the hub address. Note that in production code, this method must be protected to be called by the owner only:

function init_hub(RelayHub hub_addr) public {
init_relay_hub(hub_addr);
}

In your deployment script (2_deploy_contracts.js), you should add the hub address:

var ConvertLib = artifacts.require(‘./ConvertLib.sol’)var MetaCoin = artifacts.require(‘./MetaCoin.sol’)let networks = {
‘ropsten’: {
relayHubAddr: ‘0x1349584869A1C7b8dc8AE0e93D8c15F5BB3B4B87’
},
‘development’: {
relayHubAddr: ‘0x9C57C0F1965D225951FE1B2618C92Eefd687654F’
}
}
var RelayHub = artifacts.require( ‘./RelayHub.sol’)module.exports = function (deployer, network) {
deployer.deploy(ConvertLib)
deployer.link(ConvertLib, MetaCoin)
let hubAddr = networks[network].relayHubAddr
deployer.deploy(MetaCoin).then(() => {
let hub = RelayHub.at(hubAddr)
return hub.depositFor(MetaCoin.address, { value:1e18 })
}).then(() => {
console.log(“== Initializing Metacoin’s Hub”)
return MetaCoin.at(MetaCoin.address).init_hub(hubAddr)
}).catch(e => {
console.log(‘error: ‘, e)
})
}

Notice that we also call “depositFor”. We must “fund” our contract so it will be able to pay for the incoming transaction (this, of course, works only on a local network. Later we’ll see how to fund a contract deployed to a public network, like “ropsten”).

At this point, the contract can accept relayed calls. However, all requests will look as if they came from a single address — the RelayHub, since that is what the msg.sender Solidity variable is set to. We can’t modify msg.sender, so we provide a helper method instead. You should use the get_sender() anywhere in your contract where msg.sender was previously used.

Don’t worry — for normal calls (non-relayed), it simply returns msg.sender. But for calls relayed through our RelayHub, it returns the real caller.
For msg.data, you can use get_message_data() similarly.

For example, a coin balance method might look like:

function getBalance() public view returns(uint) {
return balances[get_sender()];
}

That’s it! Your contract now supports relayed calls. Note that the contract can still be used directly, without a relay.

Client Modifications

Now let’s modify our client application (app/scripts/index.js).

At the beginning of the app, add:

const tabookey = require( ‘tabookey-gasless’)const RelayProvider = tabookey.RelayProvider

And, at your app startup (start function) you do:

var provider= new RelayProvider(web3.currentProvider, {
txfee:12,
force_gasLimit : 500000
})
web3.setProvider(provider)

That’s all the code changes you need to do. From this point on, any contract you load into your application will be invoked via a relay, and not costing your callers anything. The app will continue to use Metamask, but for signing only.

To run the app, you should make sure the “gsn-dock-relay-ganache” (explained above) is running in another window, and then run:

npx truffle migrate
npm run dev

All the above sources are checked in as “with-relay” branch on the sample repo.

Fund Your Contract on a Public Network

At this point, you might think “oh, it’s great that my client doesn’t have to pay for the gas — but who does?”. Well, your contract pays for the gas. It can’t pay directly, so you must deposit some ether into the RelayHub on behalf of your contract so that it will be able to pay the relay for incoming calls.

In the ganache-based sample above, we added a call for RelayHub.depositFor() to fund the contract. This will also work if you use truffle migrate to deploy your contract to a public network (like ropsten). However, even if you do, over time as more calls are made to your contract, the deposit will deplete, and you’ll have to refund your contract.

For this purpose, we created a “Contract Manager” tool, to check the balance and deposit more ether for a contact.

Working Without Metamask

Once we don’t need the client’s wallet, there’s no real reason to require Metamask installed. Local addresses, kept in the browser (as a cookie) might be enough. This way, the dapp can be used from any browser, including any mobile browser.

Since we will not dive into this setup, you can check the source modification at the “MetaCoin” sample, on the relay-without-metamask branch. The sample let the user decide whether to use Metamask, or not, and if not, which network to connect, xdai or ropsten. Then it creates a temporary key and saves it into a cookie.

Protecting Your Contract

The above scheme works but leaves your contract’s RelayHub deposit vulnerable. Anyone can generate as many calls as they want, and thus deplete your contract’s deposit, preventing calls from legitimate users.

For example, we can let the user pay for the transaction with our tokens:

The accept_relayed_call()method can validate the user holds tokens, and the post_relayed_call() will actually deduct these tokens from his account.
These tokens are not removed if the user uses direct (without a relay) call to the contract, and pays for the transaction with ether.

Note that payments to the relays are still in ether, so you as a contract owner must be very careful about handling the conversion rate. You can see a sample contract for this here, as well as other caller verification methods.

Summary

In the above article, we’ve explained how a dapp can use the TabooKey Gas Station Network as a relay for gasless transactions, in order to allow client calls without paying for gas, and letting the dapp to decide how to get payments from its clients.

In future articles, we’ll try to explain the technical details of the relay network, and most importantly, why it is secured and why no participant of the network (client, relay or contract) can steal or block any other entity.

If you have questions or suggestions, please feel free to comment here.

--

--