
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 , 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
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)
// 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
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 = ,
‘development’:
}
var RelayHub = artifacts.require( ‘./RelayHub.sol’)
module.exports = function (deployer, network) )
}).then(() => ).catch(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)
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, )
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 “” 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.
- Open the URL
- Enter the address of your contract
- Click “recheck” to check the current deposit balance
- Enter the eth balance you want, and click Deposit
- Approve the money transfer in your Metamask
Working Without Metamask
Once we don’t need the client’s , 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 “” 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 :
The accept_relayed_call()method can validate the user holds , and the post_relayed_call() will actually deduct these from his account.
These 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 , 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.
Published at Fri, 15 Feb 2019 00:54:51 +0000