Skip to main content

Frontend Changes

The dApp's frontend needs to be updated to integrate Atlas and the module deployed in the module deployment section. This can be easily done using the Atlas TypeScript SDK, which is compatible with internet browsers.

info

A full example of integration is available in the demo repository.

Requirements

The dApp should be given an auctioneer Ethereum address for this module (every module has a unique auctioneer signer attributed).

Initialize the SDK

import { AtlasSdk, FastlaneBackend } from "@fastlane-labs/atlas-sdk";
import { JsonRpcProvider } from "ethers";

// The following values must be adjusted
const chainId = 11155111;
const rpcUrl = "https://rpc.sepolia.org";
const atlasAuctioneerUrl = "https://auctioneer.atlas.fastlane.xyz";

// Create the SDK instance
const sdk = await AtlasSdk.create(
new JsonRpcProvider(rpcUrl, chainId),
chainId,
new FastlaneBackend({endpoint: atlasAuctioneerUrl})
);

Approve the Execution Environment to spend the user's token

Instead of approving the dApps router, the user needs to approve their execution environment to transfer ERC20 tokens from them. The execution environment is unique to each user/module pair. It acts as their "smart wallet". This can be done only once if we set the allowance to the max possible value, although this is considered bad practice.

const userAddress = "0x01";     // User account address
const moduleAddress = "0x01"; // Address of the module (dAppControl contract)

const executionEnvironment = await atlasSdk.getExecutionEnvironment(
userAddress,
moduleAddress
);

// At this point, the frontend must prompt the user to approve the `executionEnvironment` as
// spender of the ERC20 tokens the user is about to swap (sell)
info

If the user is selling ETH, there is no need to approve the execution environment.

Create an Atlas user operation

Instead of having the user send a transaction to the router contract, the dApp must create an Atlas user operation. All the steps leading to the former transaction can be kept, as we still need its data field. The former transaction.data will be packed into a user operation.

// This must be communicated by the Atlas auctioneer (Fastlane)
const auctioneerAddress = "0x01";

// Assuming the frontend has built the data to be sent to the router contract.
// It should contain the function selector (swap function) and its calldata.
// This use to be the data contained in `transaction.data`.
const data = ""; // Assuming not empty

const userOperation = await sdk.newUserOperation(
{
from: userAddress, // User account address
value: 0n, // This is always 0 for this module
gas: 1000000n, // Gas limit for the user operation - must be computed by the dApp
maxFeePerGas: 1000000000n, // Max fee per gas for the user operation - must be computed by the dApp
deadline: 0n, // As block number, 0 for no deadline
dapp: "0x01", // Address of the dApp's router (former `transaction.to`)
control: moduleAddress, // Address of the module (dAppControl contract)
sessionKey: auctioneerAddress, // The address of the auctioneer, which will sign the dApp operation exclusively for this module
data: data // Data to be passed to the dApp's router (former `transaction.data`)
}
);

Send the user operation to the Atlas auctioneer

The next step is sending the generated user operation to the Atlas auctioneer, unsigned. Atlas allows the user operation to remain unsigned when the signer of the dApp operation is the same one as the session key specified in the user operation. This gives us the ability to skip a user interaction (signature request popup).

For this module, it's not desirable for solvers to receive the full data of the user operation. This is mainly to avoid frontrunning. Instead, solvers will receive hints, in the form of addresses of pools where the user's swap will be routed. This needs to be specified when submitting the user operation to the Atlas auctioneer.

// Assuming the frontend generated the user operation's `data` fields, it must be able
// to store all the liquidity pools addresses where the swap will be routed to
const pools = ["0x01", "0x02"]; // The order does not matter, the auctioneer will randomize it anyway

const bundle = await sdk.submitUserOperation(userOperation, { "pools": pools });

Send the Atlas transaction to the blockchain

At this point, we got back everything we need from the Atlas auctioneer:

  • The solver operations, if any
  • The dApp operation

And we already have our user operation.

The next step is generating the calldata, and let the user sign and send the final Atlas transaction.

const eoaClient; // Assuming this is a RPC client

// If the user is selling ETH, the value should be set to the amount
// they are selling. If selling an ERC20 token, this can be set to 0
const value = 0n;

// Getting calldata
const metacallCalldata = atlasSdk.getMetacallCalldata(
bundle.userOperation,
bundle.solverOperations,
bundle.dAppOperation
);

// Getting the right gas limit
const metacallGasLimit = await atlasSdk.getMetacallGasLimit(
bundle.userOperation,
bundle.solverOperations
);

// Sending the transaction
const hash = await eoaClient.sendTransaction({
to: await sdk.getAtlasAddress(),
value: value,
gas: metacallGasLimit,
// Matching the max fee per gas set in the user operation
maxFeePerGas: bundle.userOperation.getField("maxFeePerGas").value as bigint,
data: metacallCalldata,
});