Skip to content
Information Circle

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 TermSpanning Equivalent
msg.senderspanningMsgSender()
_msgSender() [openZeppelin]spanningMsgSender()
tx.originspanningTxnSender()

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:

FunctionDescription
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:

FunctionDescription
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:

ModifierDescription
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.