Pay attention to named vs. alt VMs

We classify chains by their execution environment. Our routing and transaction tracking primarily differs based on whether chains in your quote request include named vs. alt VMs. The /getAction response includes a vmId field that informs you which routing strategy will applies to your quote. You can also find this information on the Supported Chains page. When the vmId is alt-vm, please note that you must:
  1. Use the /getPaths endpoint to validate your destination token andj source token swap amount (reference).
  2. Register your transaction after broadcasting to initiate tracking (reference).

Cache available paths

The /getPaths endpoint returns all available destinations for a given source token and set of filters. For alternative VMs, there may be minimum or maximum transfer amounts, or only a subset of the total tokens available on a network may be supported. We recommend caching available paths with ~15 minute expiries and checking against it as part of the quoting process to ensure your requests are executable. For named VMs, it is safe to assume any token in any size is available.

Check for approvals

If you select an ERC20 token as the source token to complete a transaction, you will need to make sure the user has granted approval on the source chain to spend the required amount for the transaction. EIP-7702 enables you to combine the approval and broadcast transactions into a single call. For a basic example of the legacy approval method, please visit this NFT mint button example.

Manage permissioned functions

Some smart contracts include permissioned functions that resolve based on the wallet address sending the transaction. Often, you will see a check based on the msg.sender. Cross-chain transactions are executed from relayer accounts, not directly from the user’s wallet. As a result, a msg.sender check will fail even if it actually is the authorized wallet address sending the source chain transaction. msg.sender checks are typically impractical in a multi-chain context. Instead, we recommend a signature-based approach to authentication. For example, the Songcamp team wanted users to mint NFTs without metadata and then send a permissioned function to write metadata to the NFT based on certain traits of the user’s wallet address. To enable cross-chain execution while still authenticating wallets, Songcamp included a hash of the user’s wallet address in the transaction calldata and recovered the singer’s address as follows: View full contract
  function multiWriteToDiscSignature( 
      uint256[] memory tokenIds, 
      uint256[] memory songSelections, 
      bytes memory signature
  ) public {
      require(
          tokenIds.length == songSelections.length,
          "tokenIds and songSelections arrays must have the same length"
      );

      //Constructing the signed hash for signer address recovery
      bytes32 messageHash = keccak256(abi.encodePacked(tokenIds, songSelections));  

      bytes32 ethSignedHash = keccak256(  
          abi.encodePacked("\x19Ethereum Signed Message:\n32", messageHash)  
      );  

      address signer = recoverSigner(ethSignedHash, signature);  

      for (uint256 i = 0; i < tokenIds.length; i++) {
          uint256 tokenId = tokenIds[i];
          uint256 songChoiceId = songSelections[i];

          // Check if CdMemory is not written before allowing an update
          CdMemory storage cd = readCdMemory[tokenId];
          require(
              ownerOf(tokenId) == signer,
              "Only the owner can set CdMemory"
          );
          require(
              songChoiceId >= 1 && songChoiceId <= 5,
              "Invalid song choice ID"
          );
          require(!cd.written, "One or more tokens are already written.");

          // Update CdMemory and mark it as written
          cd.writerAddress = signer;
          cd.songChoiceId = songChoiceId;
          cd.written = true;

          writeCount += 1;

          emit CdMemorySet(tokenId, signer, songChoiceId);
      }
  }

  //Helper function to determine signer address based on the signed hash and the signature

  function recoverSigner( 
      bytes32 ethSignedHash, 
      bytes memory signature
  ) public pure returns (address) { 
      // Extract the r, s, and v values from the signature
      (bytes32 r, bytes32 s, uint8 v) = splitSignature(signature); 

      // Recover and return the signer address
      return ecrecover(ethSignedHash, v, r, s); 
  }

  //Helper Function to split signature into RSV values
  function splitSignature( 
      bytes memory signature
  ) public pure returns (bytes32 r, bytes32 s, uint8 v) { 
      require(signature.length == 65, "Invalid signature length"); 

      assembly { 
          // Slice the r, s, and v components from the signature
          r := mload(add(signature, 32)) 
          s := mload(add(signature, 64)) 
          v := byte(0, mload(add(signature, 96))) 
      } 
  }