Getting Started
1. Introduction
In this tutorial, we’ll demonstrate how to implement an extended version of the Counter.sol
contract, inspired by the default Foundry example. Unlike a traditional counter deployed on a single chain, our counter will be deployed across multiple chains, and we will interact with it in a chain-abstracted fashion.
This example highlights how to abstract away blockchain-specific details, enabling seamless contract interactions across multiple chains through the SOCKET Protocol. By leveraging chain abstraction, developers can focus on application logic without worrying about the complexities of inter-chain communication.
You’ll learn how to
- Set up the environment with pre-configured contracts.
- Extend a simple
Counter
contract into a chain-abstracted version. - Walk through a
CounterAppGateway
contract to orchestrate updates on multiple counter instances.
2. Setting Up Your Environment
-
Clone the Starter Kit
The repository includes pre-built examples of
Counter
andCounterAppGateway
contracts.git clone https://github.com/SocketDotTech/socket-starter-kit
cd socket-starter-kit -
Install Dependencies
Use forge to install the required libraries.
forge install
tipMake sure foundry is atleast on following version. Pay attention to the date part.
forge 0.2.0 (9a0f66e 2024-09-26T00:20:35.649925000Z)
-
Set Up Environment Variables
Copy the provided
.env.sample
file and set proper values for private key and rpc.You can get the rpc and other details here.
cp .env.sample .env
vi .env -
Get offchainVM ETH
To pay for the transactions on offchainVM you need native tokens. You can get offchainVM ETH using the bridge or you can get ETH directly on offchainVM using the faucet.
-
Deploy the all contracts on the offchainVM and on chain instances
This command deploys all contracts on offchainVM. It includes the
Counter
,CounterDeployer
,CounterAppGateway
. These contracts collectively dictate how your app instance on each chain has to be deployed and composed.forge script script/Deploy.s.sol --broadcast --skip-simulation
You will see the deployed addresses in script logs under names
Counter Deployer
,Counter AppGateway
.tipPlease ensure you have
--skip-simulation
on the above command otherise Foundry may overestimate how much it takes to deploy.Add the deployed addresses in env for using in rest of the tutorial
export COUNTER_DEPLOYER=<Counter Deployer Address>;
export COUNTER_APP_GATEWAY=<Counter App Address>; -
Set up fees.
In this example we will be paying fees on Arbitrum Sepolia as configured in
script/Deploy.s.sol
.To pay for this increment counter transaction, deposit
arbsepETH
to the contract address of thePayloadDeliveryPlug
by running:cast send 0x804Af74b5b3865872bEf354e286124253782FA95 "deposit(address,uint256,address)" \
0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE \
<AMOUNT> \
$COUNTER_APP_GATEWAY \
--value <AMOUNT> \
--rpc-url $ARBITRUM_SEPOLIA_RPC \
--private-key $PRIVATE_KEYReplace
<AMOUNT>
in wei with more than 0.01 ETH. Please ensure the wallet you are using has at least 0.01 Arbitrum Sepolia ETH. Feel free to use any of the supported chains and run the command accordingly. You can pay using any token on a chain of your choice that has aPayloadDelivery
contract. You can deposit them to aPayloadDelivery
on any chain by calling thedeposit
function. Find all about the availablePayloadDelivery
addresses here and about fees heretipDon't forget to export
ARBITRUM_SEPOLIA_RPC
if you do not have it in your environment yet. -
Deploy onchain contracts
forge script script/deployOnchain.s.sol --broadcast --skip-simulation
Let's ensure that the funds have been spent to pay for the transaction by running,
https://apiv2.dev.socket.tech/getDetailsByTxHash?txHash=<TX_HASH>
Replace
<TX_HASH>
with the last transaction executed and ensure status isCOMPLETED
. If you want to monitor all transactions at the same time you can run:node script/transactionStatus.js deployOnchain
-
Increment multiple counters
To increment the various counters deployed on all different chains by different values we will run,
forge script script/incrementCounters.s.sol --broadcast
Read here to learn more about how forwarder addresses are assigned on the offchainVM to represent onchain contracts.
If you want to know when the transaction is complete you can run the command below or directly use the API as described in the previous step.
node script/transactionStatus.js incrementCounters
-
Check that the counters on chain have incremented
forge script script/checkCounters.s.sol
3. Understanding the Components
Counter
This is the instance of the app that is deployed on chain. Unlike a normal counter, the increase
function of this counter is called via SOCKET.
contract Counter is Ownable(msg.sender) {
address public socket;
uint256 public counter;
modifier onlySocket() {
require(msg.sender == socket, "not socket");
_;
}
function setSocket(address _socket) external onlyOwner {
socket = _socket;
}
function getSocket() external view returns (address) {
return socket;
}
function increase() external onlySocket {
counter++;
}
}
CounterAppGateway
CounterAppGateway
is an AppGateway
. It is a contract deployed on offchainVM and not on chain. It dictates how the onchain contracts are called and composed. In this example when someone calls the incrementCounters
function, it internally triggers calls to increase
function on each provided instance. This is an onchain write triggered from AppGateway. You can also make read calls to the chains to use their state.
contract CounterAppGateway is AppGatewayBase {
constructor(
address _addressResolver,
address deployerContract_,
FeesData memory feesData_
) AppGatewayBase(_addressResolver) {
addressResolver.setContractsToGateways(deployerContract_);
_setFeesData(feesData_);
}
function incrementCounters(address[] memory instances) public async {
for (uint256 i = 0; i < instances.length; i++) {
Counter(instances[i]).increase();
}
}
function setFees(FeesData memory feesData_) public {
feesData = feesData_;
}
}
CounterDeployer
The Deployer contract is deployed to offchainVM and indicates how app contracts are to be deployed and initialized on a chain. You can read more about chain abstracted deployments here.
contract CounterDeployer is AppDeployerBase {
bytes32 public counter = _createContractId("counter");
constructor(
address addressResolver_,
FeesData memory feesData_
) AppDeployerBase(addressResolver_) {
creationCodeWithArgs[counter] = type(Counter).creationCode;
_setFeesData(feesData_);
}
function deployContracts(uint32 chainSlug) external async {
_deploy(counter, chainSlug);
}
function initialize(uint32 chainSlug) public override async {
address socket = getSocketAddress(chainSlug);
address counterForwarder = forwarderAddresses[counter][chainSlug];
Counter(counterForwarder).setSocket(socket);
}
function setFees(FeesData memory feesData_) public {
feesData = feesData_;
}
}