How to pay fees for token transfers from another address

Custodial blockchain address for your native assets, ERC-20, ERC-721 and ERC-1155 tokens.

If you’ve built or want to build a custodial exchange like Binance or Kraken, a custodial wallet service, or a custodial NFT marketplace, there’s one major headache that you’re bound to encounter: how do you pay the gas fees for sending tokens from your users’ accounts? Astoundingly, there really hasn’t been any simple way to do this until now. As an exchange owner, every time you want to send tokens from one of your user’s accounts, you have to calculate the gas fees, send that amount to their account (a transaction that also incurs gas fees), and send the tokens from their account.

Two transactions, both of which incur gas fees, plus can result in minuscule differences of crypto “dust” remaining in their accounts. If you have thousands or millions of addresses, this method creates an enormous amount of work and fees for you as a custodial provider.

In short, a big headache. So we decided to fix that.

Custodial wallet Smart contract

Our new custodial multi-wallet uses a master address that generates user sub-accounts - smart contracts.

Smart contract implementation is available here.

You simply top up the master address with a balance of crypto from which you’ll pay all of your gas fees, and every time you send tokens from a user address, the gas fees are deducted from the master balance. As with everything else in Tatum, it’s super easy to implement.

Generate new custodial wallet contract

The first step is to create a smart contract, that will accept all the assets you want. We have multiple options and combinations, which can be used. Once you choose a setup, it can't be changed in the future. Supported features are:

  • Accept native assets (ETH, CELO, MATIC, BSC, ONE) and ERC-20 tokens

  • Accept native assets (ETH, CELO, MATIC, BSC, ONE) and ERC-721 tokens

  • Accept native assets (ETH, CELO, MATIC, BSC, ONE) and ERC-1155 tokens

  • Accept native assets (ETH, CELO, MATIC, BSC, ONE), ERC-20, and ERC-721 tokens

  • Accept native assets (ETH, CELO, MATIC, BSC, ONE), ERC-20, and ERC-1155 tokens

  • Accept native assets (ETH, CELO, MATIC, BSC, ONE), ERC-721, and ERC-1155 tokens

  • Accept native assets (ETH, CELO, MATIC, BSC, ONE), ERC-20, ERC-721, and ERC-1155 tokens

All of these combinations can be enabled in a batch mode - it will be possible to transfer an unlimited amount of different tokens in 1 blockchain transaction atomically. Otherwise, you need to transfer the assets one by one. The more features you enable, the more gas you will pay for initial address creation.

Enough of the talk, let's generate one on the Polygon Mumbai testnet. We will enable all features by default.

Request
Response
Request
curl --location --request POST 'https://api-eu1.tatum.io/v3/blockchain/sc/custodial' \
--header 'Content-Type: application/json' \
--header 'x-api-key: YOUR_API_KEY' \
--data-raw '{
"enableFungibleTokens": true,
"enableNonFungibleTokens": true,
"enableSemiFungibleTokens": true,
"enableBatchTransactions": true,
"chain": "MATIC",
"fromPrivateKey": "0x37b091fc4ce46a56da643f021254612551dbe0944679a6e09cb5724d3085c9ab"
}'
Response
{
"txId": "0x2979a3d30f812b0d9ebcc66d01b620d3779380c38e8770a82fa4ea4f2dfa8f69"
}

As a result, we obtain transaction hash. From this hash, we can find the address of the contract - our custodial address.

Request
Response
Request
curl --location --request GET 'https://api-eu1.tatum.io/v3/blockchain/sc/address/MATIC/0x2979a3d30f812b0d9ebcc66d01b620d3779380c38e8770a82fa4ea4f2dfa8f69' \
--header 'Content-Type: application/json' \
--header 'x-api-key: YOUR_API_KEY'
Response
{
"contractAddress": "0x4eC40a4A0dA042d46cC4529f918080957003b531"
}

You can assign this address to your user. It is capable of accepting MATIC and any ERC-* tokens out of the box. You can take a look at our address on Mumbai Polygon explorer.

Transfer asset from the custodial address

Your user deposited some ERC-20 or ERC-721 token on his custodial address. Now it's time to move assets to another address. You don't have to send any MATIC there to pay for gas, just tell, which assets should be sent, how much of it, and where. Let's send an ERC-721 token with tokenId 100 out of the wallet.

Request
Response
Request
curl --location --request POST 'https://api-eu1.tatum.io/v3/blockchain/sc/custodial/transfer' \
--header 'Content-Type: application/json' \
--header 'x-api-key: YOUR_API_KEY' \
--data-raw '{
"chain": "MATIC",
"contractType": 1,
"tokenId": "100",
"tokenAddress": "0x6d8eae641416B8b79e0fB3a92b17448CfFf02b11",
"custodialAddress": "0xA09Eee86868ECE1A44B57b1EEBEe4f40Df2Ed43D",
"recipient": "0x8cb76aEd9C5e336ef961265c6079C14e9cD3D2eA",
"fromPrivateKey": "0x37b091fc4ce46a56da643f021254612551dbe0944679a6e09cb5724d3085c9ab"
}'
Response
{
"txId": "0xa1bdc0008b6cce138cec38336a3cbbf1117cc56bcfe0004e14d6789a9d099b72"
}

As a result, we obtain transaction hash. You can see, that we have to enter the address of the token we want to transfer, and for ERC-721 also it's tokenId. Important is a contractType attribute, which tells the wallet, which kind of a token it is transferring.

Since the transfer endpoint is universal and ERC-20 tokens or native assets don't have tokenId, it can be omitted. Similarly, for amount, ERC-721 tokens are always one-of-a-kind, so the amount is omitted. For native assets, we don't have a contract address, so it is omitted.

Transfer asset from the custodial address in a batch call

Simple transfers are fine, but much more powerful are batch transfers. In this operation, you can basically transfer all of the assets from that wallet in 1 transaction. You can define different recipients for different assets, you can split the balance of the ERC-20 token into 2 different recipients and many more.

We are transferring all types of assets we have in the custodial wallet in 1 call:

  • contractType 0 - ERC-20 token

  • contractType 1 - ERC-721 token

  • contractType 2 - ERC-1155 token

  • contractType 3 - native asset - MATIC

Request
Response
Request
curl --location --request POST 'https://api-eu1.tatum.io/v3/blockchain/sc/custodial/transfer/batch' \
--header 'Content-Type: application/json' \
--header 'x-api-key: YOUR_API_KEY' \
--data-raw '{
"chain": "MATIC",
"contractType": [0,1,2,3],
"tokenId": ["0","100","1","0"],
"amount": ["0.001","0","1","0.009"],
"tokenAddress": [
"0x2d7882bedcbfddce29ba99965dd3cdf7fcb10a1e",
"0x6d8eae641416B8b79e0fB3a92b17448CfFf02b11",
"0x664F97470654e8f00E42433CFFC0d08a5f4f7BC7",
"0"
],
"custodialAddress": "0x4eC40a4A0dA042d46cC4529f918080957003b531",
"recipient": [
"0x8cb76aEd9C5e336ef961265c6079C14e9cD3D2eA",
"0x8cb76aEd9C5e336ef961265c6079C14e9cD3D2eA",
"0x8cb76aEd9C5e336ef961265c6079C14e9cD3D2eA",
"0x8cb76aEd9C5e336ef961265c6079C14e9cD3D2eA"
],
"fromPrivateKey": "0x37b091fc4ce46a56da643f021254612551dbe0944679a6e09cb5724d3085c9ab"
}'
Response
{
"txId": "0xc1e1b725d68a5e35d36121307d2365678ad9179411c1f59a2deac8a7080afffa"
}

As a result, we again obtain transaction hash.

When the asset you want to transfer does not have tokenAddress or amount or tokenId, use 0 instead. We will take care of the rest for you.

To find out more about the API calls we have just used, visit our API Reference.