Comment on page
🌉
Axelar Transfer
Smart contracts for bridging from Agoric to Ethereum & Avalanche
Limited Developer Support
All assets represented in this library are community built, which means limited support from the Agoric OpCo development team. Please use components, APIs, and front-ends with caution.
Sending cross-chain messages from Agoric
The Axelar protocol enables decentralised cross-chain communication between different networks, allowing to send arbitrary data and/or tokens.
Axelar decentralisation is achieved by its own proof-of-stake blockchain network built using Cosmos SDK with permissionless set of validators, which constantly produce blocks containing cross-chain transactions info.
This component relies on Pegasus package that enables pegging of Agoric ERTP assets to or from remote assets. This is achieved by using the IBC (Inter-Blockchain Communication) protocol supported by Agoric.
This component allows tokens and/or arbitrary data to EVM chains. This enables a lot of use cases, like token/information bridging, response to Agoric on-chain events on EVM chain and etc.
There are some previous considerations to have before instantiating the Axelar transfer contract.
In order to start an instance of the Axelar-Transfer contract it is not required any issuerKeywordRecord, terms, or privateArgs. Only the installation reference. Although, there is a need to establish an IBC and Pegasus transfer channel between the Agoric and Axelar, prior to the execution of the contract methods.
The
publicFacet
of the contract contains only setupAxelar
method that runs the Axelar setup process and returns a function sendGMP
that can be used to send arbitrary data to EVM chains. The creator facet is empty and has no methods.setupAxelar
requires pegaus
parameter, which is pegasus service instance.const publicFacet = Far('publicFacet', {
// Public faucet for anyone to call
/**
* This is a contract to interact with Axelar and send messages and tokens from Agoric to EVM's with Axelar
*
* @param {import('@agoric/pegasus').Pegasus} pegasus Pegasus public facet
*/
setupAxelar: async (
pegasus
) => {
const ret = await setupAxelar(
pegasus
);
return ret;
},
});
This function will trigger cross-chain call to EVM chain with specified target address, value and payload. Metadata object should contain information about destination chain, address and payload that will be passed to EVM contract as calldata.
sendGMP
function creates an invitation to transfer assets over network with all cross-chain info included. The Pegasus package is modified in the required version of Agoric SDK so that it could accept additional sender
parameter for the Axelar protocol. The offer then is issued and then sendGMP
returns an offer result.export const setupAxelar = async (
pegasus,
) => {
// ...
return Far('axelar', {
/**
* Sends a GMP message to an EVM chain from Agoric.
*
* @param {ZoeService} zoe - Zoe service instance
* @param {Purse} purse - Purse for `amount` payment
* @param {Peg} peg - Pegasus connection instance
* @param {string} receiver - Pegasus receceiver address
* @param {string} sender - Pegasus sender address
* @param {NatValue} amount - Amount of tokens to be sent (0 if only data is sent)
* @param {Metadata} metadata - object containing destination chain, address and payload info
* @returns {Promise<any>}
*/
sendGMP: async (zoe, purse, peg, receiver, sender, amount, metadata) => {
/** @type {import('@agoric/pegasus').Pegasus} */
const pegasus = await storeConnection.get("pegasus");
const memo = JSON.stringify(metadata);
// Create a Zoe invitation with cross-chain message info
// to transfer assets over network to a deposit address
const [invitation, brand] = await Promise.all([
E(pegasus).makeInvitationToTransfer(peg, receiver, memo, sender),
E(peg).getLocalBrand()
]);
const amt = harden({ brand, value: amount });
const pmt = await E(purse).withdraw(amt);
const seat = E(zoe).offer(
invitation,
harden({ give: { Transfer: amt } }),
harden({ Transfer: pmt }),
);
const result = await E(seat).getOfferResult();
return result
}
}
For sending only messages without tokens to the remote chain, use
"type": 1
in metadata:// create metadata (note you have to abi encode payload)
let payload = [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,32,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,185,77,138,47,92,174,154,148,168,212,54,75,162,212,199,126,135,33,159,249]
/** @type {Metadata} */
const metadata = {
payload,
"type": 1,
"destination_chain": "avalanche",
"destination_address": "0xF4799D77Cc7280fd3Bd9186A7e86B0540243E32d"
}
// Lets send a GMP message
await E(axelar).sendGMP(
home.zoe,
localPurseP,
peg,
'axelar1dv4u5k73pzqrxlzujxg3qp8kvc3pje7jtdvu72npnt5zhq05ejcsn5qme5',
'agoric1m26r7lnp422hftlspza6e8pkun3nvddacmu8ta',
0n,
metadata
);
For sending tokens and the payload to the remote chain, use
"type": 2
in metadata:// create metadata (note you have to abi encode payload)
let payload = [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,32,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,185,77,138,47,92,174,154,148,168,212,54,75,162,212,199,126,135,33,159,249]
/** @type {Metadata} */
const metadata = {
payload,
"type": 2,
"destination_chain": "avalanche",
"destination_address": "0xF4799D77Cc7280fd3Bd9186A7e86B0540243E32d"
}
// Lets send a GMP message
await E(axelar).sendGMP(
home.zoe,
localPurseP,
peg,
'axelar1dv4u5k73pzqrxlzujxg3qp8kvc3pje7jtdvu72npnt5zhq05ejcsn5qme5',
'agoric1m26r7lnp422hftlspza6e8pkun3nvddacmu8ta',
1000000n / 4n,
metadata
);
For sending only tokens without payload, use
"type": 3
in metadata and set "payload": null
:The initialisation and setup guide can be found in README.md file in project repository. Also check the unit test for the contract to find the testing setup process for
sendGMP
function.Be aware of specifics of calling EVM contracts - the payload that will be passed to the contract must be decodable by recipient contract. If you are attempting to call a specific function of the contract, make sure to correctly encode the function selector and the function arguments. Otherwise, your payload has to be handled and decoded manually by the
fallback()
function. Check the official Solidity documentation for more info about the encoding:```solidity
// function selector: 0x40c10f19
// e.g. if you want to mint 100 tokens (6 decimals) to
// 0x1234567890123456789012345678901234567890, the payload encoding would be:
// 0x40c10f19 + 0000000000000000000000001234567890123456789012345678901234567890 + 0000000000000000000000000000000000000000000000000000000005f5e100
// ^ ^ ^
// selector address argument `to`, padded to 32 bytes uint256 argument `amount`, padded to 32 bytes (100000000 converted to hex)
function mint(address to, uint256 amount) public {
// TODO: check msg.sender
_mint(to, amount);
}
// for manual data handling
fallback() external payable {
// TODO: check msg.sender
(
// declare variables to store decoded arguments
) = abi.decode(msg.data, (/* specify argument types */));
}
```
Last modified 2mo ago