Cross-chain metadata support

Sending metadata across chains via the pNetwork

EVM-to-EVM style pTokens bridges (such asĀ PNT-on-BSCĀ - the pNetwork token on the Binance chain) now implement metadata! At a high level, this allows you to send not only tokens across chains, but also extra data as well. Combining this feature with smart-contracts makes for all manner of interesting possibilities.By pegging in a given asset and includingĀ user_data, the pToken bridge will actually take yourĀ user_dataĀ and wrap it inĀ metadata, super-charging what can be done on the destination chain.
That metadata consists of yourĀ user_dataĀ plus some extra data including:
  • a version number to signify which metadata algorithm the bridge is invoking.
  • a chain ID. This is not to be confused with the chain ID of the blockchain your pToken is currently on, it is in fact identifying theĀ originĀ chain of the asset that has been ptokenized.
  • anĀ origin_address. This is the address whence the tokens came fromĀ on that origin blockchain.
The final metadata structure looks as follows (with solidity types):
PTokensMetadata: version bytes1, user_data: bytes, origin_chain_id: bytes4 origin_address: string,
A reference for the current originating chain IDs is as follows:
EthereumMainnet => 0x005fe7f9 EthereumRopsten => 0x0069c322 EthereumRinkeby => 0x00f34368 BitcoinMainnet => 0x01ec97de BitcoinTestnet => 0x018afeb2 EosMainnet => 0x02e7261c TelosMainnet => 0x028c7109 BscMainnet => 0x00e4b170 EosJungleTestnet => 0x0282317f XDaiMainnet => 0x00f1918e PolygonMainnet => 0x0075dd4c UltraMainnet => 0x025d3c68 FioMainnet => 0x02174f20 UltraTestnet => 0x02b5a4d6 ArbitrumMainnet => 0x00ce98c4 LuxochainMainnet => 0x00d5beb0 FantomMainnet => 0x0022af98 AlgorandMainnet => 0x03c38e67 PhoenixTestnet => 0x02a75f2c PhoenixMainnet => 0x026776fa EthereumGoerli => 0x00b4f6c5 EthereumSepolia => 0x0030d6b5
The metadata willĀ onlyĀ be passed if the destination address of your peg in is a smart-contract. Further, that smart-contract must be registered as anĀ ERC777TokenRecipientĀ via theĀ ERC1820Ā Token Registry`.
Once registered there, the PTokenĀ ERC777Ā smart-contract knows that it can mint your pegged-in tokens and transfer them to your desired smart-contract address using theĀ ERC777-specificĀ send(address recipient, uint256 amount, bytes data)Ā function. Via this, theĀ bytes dataĀ parameter will contain your metadata.Now your destination contract can implement the ERC777Ā tokensReceivedĀ hook, which will accept this user data.
The following example contract shows you how to implement just such anĀ ERC777TokenRecipient, and also how to decode your metadata:
pragma solidity 0.6.2; import "https://github.com/OpenZeppelin/openzeppelin-contracts/blob/v3.3.0/contracts/token/ERC777/IERC777.sol"; import "https://github.com/OpenZeppelin/openzeppelin-contracts/blob/v3.3.0/contracts/introspection/IERC1820Registry.sol"; import "https://github.com/OpenZeppelin/openzeppelin-contracts/blob/v3.3.0/contracts/token/ERC777/IERC777Recipient.sol"; contract PtokenWithMetadata is IERC777Recipient { IERC1820Registry private _erc1820 = IERC1820Registry(0x1820a4B7618BdE71Dce8cdc73aAB6C95905faD24); bytes32 constant private TOKENS_RECIPIENT_INTERFACE_HASH = keccak256("ERC777TokensRecipient"); event MyMetadata(bytes1 metadataVersion, bytes myUserData, bytes4 originChainId, string originAddress); constructor () public { _erc1820.setInterfaceImplementer(address(this), TOKENS_RECIPIENT_INTERFACE_HASH, address(this)); } function decodeMetadata( bytes memory metadata ) public pure returns( bytes1 metadataVersion, bytes memory userData, bytes4 originChainid, string memory originAddress ) { return abi.decode(metadata, (bytes1, bytes, bytes4, string)); } function tokensReceived( address /*operator*/, // <- Uncomment any variables you might want to use! address /*from*/, address /*to*/, uint256 /*amount*/, bytes calldata userData, bytes calldata /*operatorData*/ ) external override { (bytes1 version, bytes memory myUserData, bytes4 originChainId, string memory originAddress) = decodeMetadata(userData); emit MyMetadata(version, myUserData, originChainId, originAddress); //.. // Or, rather than just firing a boring event, do other cool things with your metadata instead! //.. } }
Now that you have all sorts of interesting information regarding the origin of your pTokenized asset, the rest is up to you!