UCS01 Relay Solidity Integration
This example demonstrates how to create a Solidity contract that calls Union’s UCS01 Relay to transfer assets.
UCS01_RELAY=0xD0081080Ae8493cf7340458Eaf4412030df5FEEb
Source code for this example can be found here: example-ucs01-solidity For this example we will be using foundry.
Install foundry
curl -L https://foundry.paradigm.xyz | bash
Directorysrc
- Transfer.sol
Directoryscript
- Transfer.s.sol
- remappings.txt
- foundry.toml
Transfer USDC on Sepolia
Let’s write a dead simple contract that transfers USDC from one address to another.
Contract Source
pragma solidity ^0.8.27;
import {IERC20} from "forge-std/interfaces/IERC20.sol";
struct LocalToken { address denom; uint128 amount;}
struct Height { uint64 revisionNumber; uint64 revisionHeight;}
interface IRelay { function send( string calldata sourceChannel, bytes calldata receiver, LocalToken[] calldata tokens, string calldata extension, Height calldata timeoutHeight, uint64 timeoutTimestamp ) external;}
contract Transfer { // https://github.com/unionlabs/union/blob/main/evm/README.md#sepolia address public constant relay = 0xD0081080Ae8493cf7340458Eaf4412030df5FEEb;
// https://github.com/unionlabs/union/blob/main/evm/contracts/apps/ucs/01-relay/Relay.sol#L54-L61 function transferAsset() public { LocalToken[] memory tokens = new LocalToken[](1); tokens[0].denom = 0x1c7D4B196Cb0C7B01d743Fbc6116a902379C7238; tokens[0].amount = 1000000;
IERC20(tokens[0].denom).approve(relay, 1000000);
IRelay(relay).send( "channel-90", hex"a833B03D8ED1228C4791cBfAb22b3ED57954429F", tokens, "", Height({revisionNumber: 100, revisionHeight: 1000000}), 0 ); }}
Contract Running Script
pragma solidity ^0.8.27;
import {Script, console} from "forge-std/Script.sol";import {Transfer} from "../src/Transfer.sol";import {IERC20} from "forge-std/interfaces/IERC20.sol";
contract TransferScript is Script { address public constant USDC = 0x1c7D4B196Cb0C7B01d743Fbc6116a902379C7238;
function run() public { uint256 privateKey = vm.envUint("PRIVATE_KEY"); vm.startBroadcast(privateKey); Transfer transfer = new Transfer();
IERC20(USDC).transfer(address(transfer), 1500000);
console.log("transferring"); transfer.transferAsset(); console.log("complete"); vm.stopBroadcast(); }}
You will be using the same RPC url across examples, so good to export it to an environment variable:
export RPC_URL="https://eth-sepolia.g.alchemy.com/v2/<YOUR_API_KEY>"
Funding the Wallet
ETH Sepolia faucets:
USDC faucet: faucet.circle.com
or pick a different token from faucet list here app.union.build/faucet
Executing the Contract
We will transfer 1 USDC from Sepolia to a Union wallet.
The first transfer we will do against a forked network using anvil
.
Forked Network Example
-
Grab a sepolia RPC url from chainlist.org. Recommended to use a premium RPC provider.
-
Start a local
anvil
fork of Sepoliaanvil --fork-url $RPC_URL -
Run the script with
forge
forge script script/Transfer.s.sol:TransferScript \--fork-url $RPC_URL \--broadcast -
we should see something like this:
[⠊] Compiling...No files changed, compilation skippedScript ran successfully.##### sepolia✅ [Success]Hash: 0x38b0fc68c482f75c1ca89e069cf18fea712131fd44c930bee03274804e9fc6b7Block: 6801346Paid: 0.042409917351145045 ETH (183391 gas * 231.254081995 gwei)
Live Sepolia Example
- Grab a sepolia RPC url from chainlist.org. Recommended to use a premium RPC provider.
- Run the script with
forge
, this will also deploy the contractforge script script/Transfer.s.sol:TransferScript \--rpc-url $RPC_URL \--private-key $PRIVATE_KEY \--broadcastdeployment example transaction – deployed contract[⠊] Compiling...No files changed, compilation skippedScript ran successfully.##### sepolia✅ [Success]Hash: 0x462a91caf0a55bbb708fbae48e930902316a8e53d2bd1a4dbcbf0e33e8d04898Contract Address: 0xA8cE7c5a7b367dF6ef6c9E33Ec56145816b9931ABlock: 6803704Paid: 0.042853127668735725 ETH (258225 gas * 165.952667901 gwei)##### sepolia✅ [Success]Hash: 0x26bc6d93323fd57315141c370ca346c10757b2eb989bffd0e9f9a91d8b2864f6Block: 6803704Paid: 0.03045895266654954 ETH (183540 gas * 165.952667901 gwei)✅ Sequence #1 on sepolia | Total Paid: 0.083627532219343524 ETH (503924 gas * avg 165.952667901 gwei)==========================ONCHAIN EXECUTION COMPLETE & SUCCESSFUL. - Optional - verify the contract
we verified this contract on Sourcifyforge verify-contract 0xA8cE7c5a7b367dF6ef6c9E33Ec56145816b9931A \src/Transfer.sol:Transfer--chain-id 11155111 \--verifier sourcify \--watch
- Query Union’s GraphQL API for the deployed contract transfers: playground link