The Spanning Demo App is live! Check it out here: https://demo.spanning.network/
We have also published the open-source demo code and related JavaScript utilities.
Custom Spanning Apps
If you are a power user and you want to build some custom functionality to your applications on top of existing base class functionalities, then reaching multichain users should be just as simple!
Integration
To make your contract capable of easily sending crosschain requests and tracking multichain users on a variety of networks, you can make your contract a Spanning contract like so:
import "@spanning/contracts/Spanning.sol" contract myContract is Spanning {
This gives you access to utilities, function modifiers, and other quality-of-life improvements via the Spanning.sol implementation.
Construction
Now you can specify the Spanning Delegate you want to access multichain functionality through at deployment via the constructor and update the address later as you choose via updateDelegate.
constructor( foo bar, foo baz, address delegate ) Spanning(delegate) {
Message Sender and Transaction Origin
Now that your application must track users via their multichain address we can no longer use msg.sender
and tx.origin
as they only provide legacy addresses. Here is a table showing the mapping to the new multichain capable accessors provided by the Spanning
type contracts:
Legacy Term | Spanning Equivalent |
---|---|
msg.sender | spanningMsgSender() |
_msgSender() [openZeppelin] | spanningMsgSender() |
tx.origin | spanningTxnSender() |
Here is an example of a function, mint()
, that has been upgraded to handle both local and multichain users:
function mint() public { - _mint(msg.sender); + _mint(spanningMsgSender()); }
Spanning Address Helper Functions
For backward compatibility and familiarity for local users, you may want to provide versions of functions that take in local legacy addresses. In these cases, Spanning Labs recommends overloading functions that take in address
type arguments and utilizing the helper function specified below:
Function | Description |
---|---|
getAddressFromLegacy(address legacyAddress) | Creates an Address from a Legacy Address, using the local domain. |
Here is an example of a function balanceOf(address account)
implemented to accept local legacy addresses:
/** * @dev Returns the amount of tokens owned by an account. * * @param accountLegacyAddress - Legacy (local) address to be queried * * @return uint256 - Token value for an account */ function balanceOf(address accountLegacyAddress) public view virtual override returns (uint256) { bytes32 derivedAddress = getAddressFromLegacy(accountLegacyAddress); return balances_[derivedAddress]; }
The following functions may also be useful for pulling information out of existing Spanning Address ownership fields:
Function | Description |
---|---|
getLegacyFromAddress(bytes32 inputAddress) | Pulls the 20-byte legacy Address from a Spanning Address. Note: this loses domain information and will now point to the legacy address on the current chain. |
getDomainFromAddress(bytes32 inputAddress) | Pulls the 4-bytes domain Id from a Spanning Address. |
Here is an example of pulling out the local address of an EVM user to withdraw native tokens from the contract to a local BNB chain wallet:
function userWithdraw() external lock { bytes32 user = spanningMsgSender(); bytes4 user_domain = SpanningAddress.getDomain(user); // Check that the Spanning User is from a Domain that uses the same // public/private key pairing. // This ensures the user from another chain can be rewarded to a Binance Wallet // correctly. require (user_domain == getDomain() || // local/BSC user user_domain == 0x00000001 || // ETH User user_domain == 0x00000089 || // MATIC user user_domain == 0x0000A86A || // AVAX user user_domain == 0x0000A4B1 || // ArETH user user_domain == 0x00000038, // BSC user - explicit "Can't withdraw native tokens to remote user from unknown network"); // Get the amount of outstanding rewards for this user uint256 withdrawAmount = this.userRewardTotal(user); // Send that user the BSC rewards (bool isUserSuccess, ) = getLegacyFromAddress(user).call{value: userWithdrawAmount}(""); require(isUserSuccess, "ERR_WITHDRAW_FAILED"); }
More helper functions and utilities are available and defined in the Spanning.sol implementation.
Function Modifiers
Spanning contracts have access to many helpful function modifiers including:
Modifier | Description |
---|---|
onlySpanning() | Reverts if the function is executed by anyone but the Delegate. |
onlyOwner() | Reverts if the function is executed by anyone but the contract owner. |