How to create a token sponsor (ERC-20)

Enable your customer's to pay for gas with ERC-20 tokens.

Overview

In most of the how to guides in the Core SDK, we provide a gasless sponsor that entirely pays for transactions on behalf of FunWallets. A Fun token sponsor does not entirely pay for transactions, instead it enables you to pay gas using ERC-20 tokens.

The Fun token sponsor works by having a supply of ETH that it can use to pay for transactions on your behalf. In exchange, it will withdraw an appropriate amount of a specific ERC-20 token from your FunWallet. This process lets you pay for a transaction using ERC-20s and in this guide, I’ll show you how to create your own token sponsor. I’ll also reference information covered in the Quick Start but I’ll quickly walk through all the steps again.

Import required objects

import {
FunWallet,
configureEnvironment,
Auth,
TokenSponsor,
generatePrivateKey,
} from "@funkit/core";

Configuring the environment

To begin, we need to set up the environment. We’ll be using the Goerli testnet and a rate-limited API key. Please visit our dashboard to receive your own production apiKey.

await configureEnvironment({
chain: "goerli",
apiKey: "ZrhepzWGxm74D0sqstuhT6dGrJxhoy8SZIToX6I3",
});

Create FunWallet for testing token sponsor

We’ll also initialize a FunWallet here that will use the token sponsor once we create it.

const privateKeyWallet = generatePrivateKey();
const funWalletAuth = new Auth({ privateKey: privateKeyWallet });
const funWallet = new FunWallet({
users: [{ userId: await funWalletAuth.getAddress() }],
uniqueId: await funWalletAuth.getWalletUniqueId(),
});

Creating a token sponsor

In order to create a token sponsor, we’ll first have to create its address. I’ll derive one from my private key.

const privateKey = generatePrivateKey();
const sponsor = new Auth({ privateKey });
const sponsorAddress = await sponsor.getAddress();

We’ll initialize the token sponsor with this private key later but for now we can actually use the sponsorAddress to re-configure environment so that it has a token sponsor. Let me quickly explain token sponsor parameters.

  1. sponsorAddress - This is the address of the token sponsor that we just created.

  2. token - This is the ERC-20 token that we want to use. Typically you can input a string like “USDC”. However, for this guide we created a custom DAI token so we’ll use the token address instead.

  3. usePermit - This enables the token sponsor to withdraw the necessary amount of DAI from your wallet to cover the transaction.

It is important to note that token sponsor is not ready for use yet, we still have to configure all the following steps before transaction can be paid for in DAI.

await configureEnvironment({
chain: "goerli",
apiKey: "ZrhepzWGxm74D0sqstuhT6dGrJxhoy8SZIToX6I3",
gasSponsor: {
sponsorAddress,
token: "0x0a1F0598A561af6b84A726bE007f581E812C3CDD",
usePermit: true,
},
});

Staking ETH into token sponsor

After re-configuring the environment, we will create the token sponsor interface and use it to deposit (stake) ETH into the sponsor. The token sponsor will use this ETH to pay for transactions on behalf of other wallets. Let me explain the staking parameters.

  1. depositor This is the address of the wallet that is transferring ETH to the sponsor. We’ll be using funds in our private key to deposit ETH into the sponsor.

  2. sponsor This is the address that we want to be designated as the sponsor. After we have created the gasless sponsor, this is the address we will call in order to use it.

  3. amount How much ETH we decide to stake into the sponsor.

Since we’re using funds in our private key to deposit ETH into the sponsor, that will be our depositor. We’ve also set our address derived from our private key as the token sponsor. This means the public address of the private key can be called to sponsor gas transactions.

// Create token sponsor interface
const tokenSponsor = new TokenSponsor();
// Stake native gas tokens to the gas sponsor
const depositAmount = 0.005;
const depositorAddress = sponsorAddress;
const depositEth = await tokenSponsor.stake(
depositorAddress,
sponsorAddress,
depositAmount
);

Make sure your private key holds ETH to stake into the sponsor.

Configuring whitelists

With a Fun sponsor, you have the ability to tune who uses it and which tokens are allowed and in this section I’ll show you the different methods to do that. However I’ll also recommend you check out How to whitelist users from a gas sponsor or How to whitelist tokens from a token sponsor for more details.

Set to whitelist mode

First, we will set the gas sponsor to whitelist mode. Whitelist mode means you choose specific wallets that are allowed to use the gas sponsor. This is useful if you want to control how many wallets can use the sponsor. For instance, you might decide to let only users who paid for a subscription use the sponsor - in this situation you want to use whitelist mode.

const whitelistMode = await tokenSponsor.setToWhitelistMode(5, sponsorAddress)const whitelistUser = await tokenSponsor.addSpenderToWhiteList(5, sponsorAddress, await funWallet.getAddress())

If no configurations on the sponsor have been set before, the sponsor will default to whitelist mode.

If the sponsor is switched to blacklist mode and switched back to whitelist mode, the whitelist will keep the last state.

Add our token to whitelist

Second, we will enable DAI as a token that can be used for gas payments on the sponsor.

const whitelistToken = await tokenSponsor.batchWhitelistTokens(
sponsorAddress,
["0x0a1F0598A561af6b84A726bE007f581E812C3CDD"],
[true]
);

Adding a FunWallet to the whitelist

Finally, we will whitelist our FunWallet, so that our FunWallet can use the sponsor.

const whitelistUser = await tokenSponsor.addSpenderToWhiteList(
5,
sponsorAddress,
await funWallet.getAddress()
);
//Send all transactions for execution
await sponsor.sendTxs([
depositEth,
whitelistToken,
whitelistMode,
whitelistUser,
]);

Using the token sponsor

Now that we have created a DAI token sponsor, the FunWallet we created at the start of this guide is set up to use DAI for gas. First we’ll add some custom DAI token to our FunWallet.

const transferTokensToWallet = await new Token(
"0x0a1F0598A561af6b84A726bE007f581E812C3CDD"
).transfer(await funWallet.getAddress(), 100);
await sponsor.sendTxs([transferTokensToWallet]);

Then execute a transaction. Note that I’m executing a transaction like normal - this is because we have already set up our environment to use the token sponsor.

const op = await funWallet.create(funWalletAuth, await sponsor.getAddress());
await funWallet.executeOperation(funWalletAuth, op);

Summary

Putting together all the pieces we have createTokenSponsor() that you can use to create a token sponsor and execute a transaction using it.

,
base-gas-token.js
import { FunWallet, configureEnvironment, Auth, TokenSponsor, Token, generatePrivateKey } from "@funkit/core"
const createTokenSponsor = async () => {
await configureEnvironment({
chain: "goerli",
apiKey: "ZrhepzWGxm74D0sqstuhT6dGrJxhoy8SZIToX6I3",
})
const privateKeyWallet = generatePrivateKey()
const funWalletAuth = new Auth({ privateKey: privateKeyWallet })
const funWallet = new FunWallet({
users: [{ userId: await funWalletAuth.getAddress() }],
uniqueId: await funWalletAuth.getWalletUniqueId()
})
const privateKey = generatePrivateKey()
const sponsor = new Auth({ privateKey })
const sponsorAddress = await sponsor.getAddress()
await configureEnvironment({
chain: "goerli",
apiKey: "ZrhepzWGxm74D0sqstuhT6dGrJxhoy8SZIToX6I3",
gasSponsor: {
sponsorAddress,
token: "0x0a1F0598A561af6b84A726bE007f581E812C3CDD",
usePermit: true
}
})
const tokenSponsor = new TokenSponsor()
const depositAmount = 0.005
const depositorAddress = sponsorAddress
const depositEth = await tokenSponsor.stake(depositorAddress, sponsorAddress, depositAmount)
const whitelistMode = await tokenSponsor.setToWhitelistMode(5, sponsorAddress)
const whitelistToken = await tokenSponsor.batchWhitelistTokens(sponsorAddress, ['0x0a1F0598A561af6b84A726bE007f581E812C3CDD'], [true])
const whitelistUser = await tokenSponsor.addSpenderToWhiteList(5, sponsorAddress, await funWallet.getAddress())
await sponsor.sendTxs([depositEth, whitelistToken, whitelistMode, whitelistUser])
const transferTokensToWallet = await new Token("0x0a1F0598A561af6b84A726bE007f581E812C3CDD").transfer(await funWallet.getAddress(), 100)
await sponsor.sendTxs([transferTokensToWallet])
const op = await funWallet.create(funWalletAuth, await sponsor.getAddress())
console.log(await funWallet.executeOperation(funWalletAuth, op))
}
createTokenSponsor()