Gelato Network
Search…
⌃K
2⃣

relayWithSponsoredUserAuthCall

SDK Method 2️⃣
WARNING: A new package release is pending for relay-context npm package. If you are using relayWithSponsoredUserAuthCall you will have to upgrade to the new version, 2.0.0.
After reading this page:
  • You'll know how to use the relayWithSponsoredUserAuthCall SDK method. This will give your user's a gasless UX requiring a user signature. This uses the 1Balance payment method, allowing you to sponsor some/all of your user's gas costs.
  • You'll learn about how to incorporate ERC2771Context into your contract for _msgSender() support.
  • You'll see some code which will help you send a relay request within minutes.

Overview

relayWithSponsoredUserAuthCall utilises authentication both via sponsor api key and a user's signature (e.g. from MetaMask) to sponsor your user's gasless transaction securely. The payment method is Gelato 1Balance.

SDK Method: relayWithSponsoredUserAuthCall

const relayWithSponsoredUserAuthCall = async (
request: SponsoredUserAuthCallRequest,
provider: ethers.providers.Web3Provider,
sponsorApiKey: string,
options?: RelayRequestOptions
): Promise<RelayResponse>

Arguments

  • request: this is the request body used to send a request.
  • provider: a valid provider connected to RPC.
  • sponsorApiKey : an API key used to authenticate your sponsorship.
  • options?: RelayRequestOptions is an optional request object

Return Object: RelayResponse

type RelayResponse = {
taskId: string;
};

Optional Parameters

Sending A Request

Request Body

const request = {
chainId: BigNumberish;
target: string;
data: BytesLike;
user: string;
userNonce?: BigNumberish;
userDeadline?: BigNumberish;
};
  • chainId: the chain ID of the chain where the target smart contract is deployed.
  • target: the address of the target smart contract.
  • data: encoded payload data (usually a function selector plus the required arguments) used to call the required target address.
  • user: the address of the user's EOA.
  • userNonce: optional, this is a nonce similar to Ethereum nonces, stored in a local mapping on the relay contracts. It is used to enforce nonce ordering of relay calls, if the user requires it. Otherwise, this is an optional parameter and if not passed, the relay-SDK will automatically query on-chain for the current value.
  • userDeadline: optional, the amount of time in seconds that a user is willing for the relay call to be active in the relay backend before it is dismissed.
    • This way the user knows that if the transaction is not sent within a certain timeframe, it will expire. Without this, an adversary could pick up the transaction in the mempool and send it later. This could transfer money, or change state at a point in time which would be highly undesirable to the user.

Example Code

Please see relay-docs-examples for more in-depth code explainers.
For your testing, Gelato has deployed a simple contract which implements logic to increment a counter with ERC2771 support.
  • CounterERC2771.sol: deployed at address 0x30d97B13e29B0cd42e6ebd48dbD9063465bF1997 on these networks.
CounterERC2771.sol's counter is special because it implements ERC-2771's _msgSender authentication to allow for secure whitelisting based on the identity of the original off-chain relay request originator, which has been verified using a user signature.
Furthermore, to set your trusted forwarder, you need the address for Gelato Relay:
  • GelatoRelay.sol: deployed at address 0xaBcC9b596420A9E9172FD5938620E265a0f9Df92 on these networks.

1. Deploy an ERC2771Context compatible contract

// SPDX-License-Identifier: MIT
pragma solidity 0.8.17;
import {
ERC2771Context
} from "@gelatonetwork/relay-context/contracts/vendor/ERC2771Context.sol";
// Importing ERC2771Context gives access to:
// 1. An immutable trusted forwarder address
// for Gelato Relay, this is 0xaBcC9b596420A9E9172FD5938620E265a0f9Df92
// 2. function isTrustedForwarder
// to verify an input address matches the trustedForwarder address
// 3. function _msgSender()
// which decodes the user's address from the calldata
// _msgSender() can now be used to refer to user safely
// instead of msg.sender (which is Gelato Relay in this case).
// 4. function _msgData()
// which decodes the function signature from the calldata
contract CounterERC2771 is ERC2771Context {
// Here we have a mapping that maps a counter to an address
mapping(address => uint256) public contextCounter;
event IncrementContextCounter(address _msgSender);
// A modifier that only allows the trustedForwarder to call
// the required target function: `incrementContext`
modifier onlyTrustedForwarder() {
require(
isTrustedForwarder(msg.sender),
"Only callable by Trusted Forwarder"
);
_;
}
// ERC2771Context: setting the immutable trustedForwarder variable
constructor(address trustedForwarder) ERC2771Context(trustedForwarder) {}
// `incrementContext` is the target function to call
// This function increments a counter variable which is
// mapped to every _msgSender(), the address of the user.
// This way each user off-chain has their own counter
// variable on-chain.
function incrementContext() external onlyTrustedForwarder {
// Remember that with the context shift of relaying,
// where we would use `msg.sender` before,
// this now refers to Gelato Relay's address,
// and to find the address of the user,
// which has been verified using a signature,
// please use _msgSender()!
// Incrementing the counter mapped to the _msgSender!
contextCounter[_msgSender()]++;
// Emitting an event for testing purposes
emit IncrementContextCounter(_msgSender);
}
}

2. Import GelatoRelaySDK into your front-end .js project

1
import { GelatoRelaySDK } from "@gelatonetwork/relay-sdk";

3. Send the payload to Gelato

This is an example using Gelato's CounterERC2771.sol which is deployed on Goerli and Polygon.
// Set up on-chain variables, such as target address
const counter = "0xEEeBe2F778AA186e88dCf2FEb8f8231565769C27";
const abi = ["function incrementContext()"];
const provider = new ethers.providers.Web3Provider(window.ethereum);
const signer = provider.getSigner();
const user = signer.getAddress();
// Generate the target payload
const contract = new ethers.Contract(counter, abi, signer);
const { data } = await contract.populateTransaction.incrementContext();
// Populate a relay request
const request = {
chainId: provider.network.chainId;
target: counter;
data: data;
user: user;
};
// Without a specific API key, the relay request will fail!
// Reach out to us on Discord to discuss getting an API key.
// Send a relay request using GelatoRelaySDK!
const relayResponse =
await GelatoRelaySDK.relayWithSponsoredUserAuthCall(request, provider, sponsorApiKey);

NB: Concurrency Support

It is important to mention that in the current implementation, the userNonce abstraction does not allow for multiple calls or call concurrency:
  • For example, if a user sends three relay calls in a row, and one of them is reverted, the userNonce mapping is still incremented, exactly as in the EVM.
  • In this case, the next two transactions, which may have relied on the previous one modifying some state, will still match the user’s specific userNonce, and this could cause the next two transactions to fail.
  • To implement multi-calls, this would have to be handled by the target smart contract via encoding into _call.data.