Skip to content

Module 10 - Building a PayStream DApp on Router Chain

Cloning the Repository

For Understanding how to Write a Smart Contract for a PayStream DApp on Router Chain, Clone the Below Repository in your Local Machine -

Terminal window
git clone https://github.com/ShivankK26/Router-PayStream .

This will Clone the PayStream DApp in the Current Directory.

File Structure

The cosmwasm Directory consists of Smart Contracts written on Router Chain and evm Directory consists of the Smart Contracts written on Polygon and Arbitrum.

  • Directorycosmwasm/
    • Directory.cargo/
      • config
    • Directorycontracts/
      • Directoryrouterpay/
        • Directoryexamples/
          • schema.rs
        • Directorysrc/
          • contract.rs
          • execution.rs
          • lib.rs
          • modifiers.rs
          • query.rs
          • reply.rs
          • state.rs
          • sudo.rs
          • tests.rs
        • Cargo.toml
    • Directorypackages/
      • Directoryrouter-pay-stream/
        • Directorysrc/
          • lib.rs
          • routerpay.rs
        • Cargo.toml
    • Directoryscripts/
      • deploy.ts
    • Directoryshell/
      • build.sh
    • Directoryutils/
      • execute.ts
      • init_wasm.ts
      • upload_wasm.ts
      • utils.ts
    • .editorconfig
    • .env.example
    • .gitignore
    • Cargo.lock
    • Cargo.toml
    • package.json
    • README.md
    • rustfmt.toml
    • yarn-error.log
    • yarn.log
  • Directoryevm/
    • Directorycontracts/
      • RouterPay.sol
    • Directoryscript/
      • deploy_on_chains.ts
      • enroll_added_chains.ts
      • enroll_on_chains.ts
      • verify_on_chains.ts
    • Directoryshell/
      • setup.sh
    • Directoryutils/
      • chain.ts
      • Deploy.ts
      • onEachChain.ts
      • types.ts
      • utils.ts
    • .env.example
    • .eslintignore
    • .eslintrc
    • .gitignore
    • .prettierrc
    • config.ts
    • hardhat.config.ts
    • package.json
    • README.md
    • tsconfig.json
    • yarn.lock

CosmWasm-side

The Router PayStream Contract is a Smart Contract that facilitates Salary Streaming between a Payer and a Payee. It enables the Creation, Management, and Withdrawal of Salary Streams across Different Chains.

This Documentation Provides a Guide for Developers to Understand and Interact with the Router PayStream Contract. It Covers the Contract’s Message Signatures, Deployment Process, and Usage Instructions.

Execute Messages

The Router PayStream Contract Supports various Messages for Different Operations. Each Message has a Specific Structure that Developers need to follow when Interacting with the Contract.

CreateStream

The CreateStream function allows the Payer to Create a Salary Stream for a Payee with or without Specifying a reason. The Parameters for this function are -

  • whitelisted_addresses: Option<Vec<(String,String)>> Addresses of Chains where the Payee can Withdraw funds.
  • start_time: u64 The Start time of the Stream.
  • pay_per_month: Uint128 The Payment amount per month, internally we Convert it in pay_per_sec.
  • recipient: String Owner of Stream who can do Whitelist Address or Blacklist.
  • remarks: Option<String> Creator can set remarks if any for Stream, e.g for what reason Stream is created.

CreateStream Message Structure:

{
"create_stream": {
"whitelisted_addresses": [
{
"chain_id": "cosmos",
"address": "cosmos1..."
},
{
"chain_id": "ethereum",
"address": "0x..."
}
],
"start_time": 1623391200,
"pay_per_month": "1000000",
"recipient": "router14rvuwugcmd94uf6ajkslwh5kc8kl5kxgdmkpze",
"remarks": "Optional remarks"
}
}

CancelStream

The CancelStream function allows the Payer to Cancel a Salary Stream at anytime. When a Stream is Canceled, any remaining ROUTE Tokens are transferred to the Payee’s Owner Address, and the Stream is Closed. The Parameters for this function are -

  • stream_id: u64 The ID of the Stream to be Canceled.
  • remarks: Option<String> Payer can Pass reason for Cancelling.

CancelStream Message Structure:

{
"cancel_stream": {
"stream_id": 1234,
"remarks": "Optional remarks"
}
}

DepositRoute

The DepositRoute function enables any User to Deposit ROUTE Tokens into the Smart Contract. This function is used to add funds to the Contract for Salary Payments.

DepositRoute Message Structure:

{
"deposit_route": {}
}

WithdrawSalary

The `WithdrawSalary` function allows the payee to initiate a withdrawal from the salary stream on the router chain or on other chain. The parameters for this function are:

  • stream_id: u64 The ID of the stream from which to withdraw.
  • recipient: String The address of the recipient who will receive the withdrawn route tokens.
  • dst_chain_id: Option<String> The chain ID to which the payee wants to withdraw the route, if ‘None’ or ‘Router Chain ID’ is passed, the route will be transferred to the router chain; otherwise, it will be transferred to the destination chain if it is enrolled, or the call will be reverted.
  • max_amount: Option<Uint128> Max amount to withdraw from salary, if passed zero or None then it will withdraw all accumulated amount

WithdrawSalary Message Structure :

{
"withdraw_salary": {
"stream_id": 1234,
"recipient": "router14rvuwugcmd94uf6ajkslwh5kc8kl5kxgdmkpze",
"dst_chain_id": "cosmos",
"max_amount": "1000000"
}
}

EnrollRemoteContract

The `EnrollRemoteContract` function allows the payee to initiate a withdrawal from the salary stream on the router chain or on other chain. The parameters for this function are:

  • chain_id: String dst chain id
  • remote_contract: String contract address on dst chain

EnrollRemoteContract Message Structure :

{
"enroll_remote_contract": {
"chain_id": "43113",
"remote_contract": "0x2C8e4027D332ac6f2210A6517c25CcE8a2c83e0e"
}
}

MapChainType

The `MapChainType` function allows the contract owner to map chain type. The parameters for this function are:

  • chain_id: String dst chain id
  • chain_type: u64 chain type of dst chain

MapChainType Message Structure :

{
"map_chain_type": {
"chain_id": "43113",
"chain_type": 1
}
}

WithdrawFunds

The `WithdrawFunds` function allows the owner to withdraw funds from the contract. The parameters for this function are:

  • recipient: String router address of recipient to which amount will be transferred
  • amount: Uint128 amount to be withdrawn

WithdrawFunds Message Structure :

{
"withdraw_funds": {
"recipient": "router14rvuwugcmd94uf6ajkslwh5kc8kl5kxgdmkpze",
"amount": "1000000"
}
}

UpdateWhiteListAddress

The `UpdateWhiteListAddress` function allows the stream owner to whitelist or blacklist a address for their stream. The parameters for this function are:

  • stream_id: u64 stream id for which this operation to be applied
  • address: String address to be whitelisted
  • chain_id: String chain id of provided address
  • to: bool true means whitelist and false means blacklist

UpdateWhiteListAddress Message Structure :

{
"update_white_list_address": {
"stream_id": 1234,
"address": "router1...",
"chain_id": "router_9601-1",
"to": true
}
}

UpdateCrossChainMetadata

The `UpdateCrossChainMetadata` function allows the ownwer of contract to update metadata such as ack_gas_limit or dst_gas_limit or relayer_fee. The parameters for this function are:

  • dst_gas_limit: Option<u64> dst gas limit for IReceive, it’s optional if passed Some(_) then only it will update the dst_gas_limit
  • ack_gas_limit: Option<u64> ack gas limit for sudo msg IAck on rotuer chain
  • relayer_fee: Option<Uint128> realyer fee for relaying the ISend message to dst chain

UpdateCrossChainMetadata Message Structure :

{
"update_cross_chain_metadata": {
"dst_gas_limit": 1000000,
"ack_gas_limit": 1000000,
"relayer_fee": "10"
}
}

Query Messages

GetContractVersion

The `GetContractVersion` function Fetches the contract version.

GetContractVersion Message Structure :

{
"get_contract_version": {}
}

GetRemoteContract

The `GetRemoteContract` function Fetches the contract address of provided chainid. The parameters for this function is:

  • chain_id: String dst chain id for which the query is being made.

GetRemoteContract Message Structure :

{
"get_remote_contract": {
"chain_id": "43113"
}
}

GetRouterPayMetadata

The `GetRouterPayMetadata` function Fetches the metadat mapped to provided stream id. The parameters for this function is:

  • stream_id: u64 stream id for which the query is being made.

GetRouterPayMetadata Message Structure :

{
"get_router_pay_metadata": {
"stream_id": 1234
}
}

GetOwner

The `GetOwner` function Fetches the contract owner.

GetOwner Message Structure :

{
"get_owner": {}
}

GetCrossChainMetadata

The `GetCrossChainMetadata` function fetches the crosschain metadata contains dst_gas_limit,ack_gas_limit and relayer_fee.

GetCrossChainMetadata Message Structure :

{
"get_cross_chain_metadata": {}
}

GetStreams

The `GetStreams` function fetches stream metadata for the range provided. The parameters for this function are:

  • from: u64 stream id from where to fetch
  • to: Option<u64> end stream id to where to fetch if not provided then it will fetches next 10 streams

GetStreams Message Structure :

{
"get_streams": {
"from": 0,
"to": 100
}
}

GetStreamWhiteListAddress

The `GetStreamWhiteListAddress` function fetches all whitelist address provided stream id, returns Vec<(String,String)>. The parameters for this function is:

  • stream_id: u64 stream id for which the query is being made.

GetStreamWhiteListAddress Message Structure :

{
"get_stream_white_list_address": {
"stream_id": 1234
}
}

GetUserStreamIds

The `GetUserStreamIds` function fetches all stream ids associated with a specific router address. The parameters for this function is:

  • address: String user router address to get all stream ids

GetUserStreamIds Message Structure :

{
"get_user_stream_ids": {
"address": "router..."
}
}

GetUserStreamsInfo

The `GetUserStreamsInfo` function fetches detailed information about the streams associated with a specific address. The parameters for this function is:

  • address: String user router address to get all stream ids

GetUserStreamsInfo Message Structure :

{
"get_user_streams_info": {
"address": "router14rvuwugcmd94uf6ajkslwh5kc8kl5kxgdmkpze"
}
}

IsWhiteListed

The `IsWhiteListed` function checks if a specific address is whitelisted for a particular stream on a specific chain. The parameters for this function are:

  • stream_id: u64 stream if for which this checks applies
  • chain_id: String chain id where address belongs to
  • address: String address for provided chain id

IsWhiteListed Message Structure :

{
"is_white_listed": {
"stream_id": 1234,
"chain_id": "43113",
"address": "0x5561b5eaa45573011343545e3756a6735899dff3"
}
}

GetAccumulatedAmount

The `GetAccumulatedAmount` function fetches the accumulated amount for a specific stream. The parameters for this function is:

  • stream_id: u64 stream if for which this checks applies

GetAccumulatedAmount Message Structure :

{
"get_accumulated_amount": {
"stream_id": 1234
}
}

These functions and their functionalities form the core of Router Pay Streaming, allowing for the creation, management, and withdrawal of salary streams on different chains.

EVM-side

RouterPay.sol

Router PayStream is a DApp to provide a Convenient Way for Employees to Receive their Salaries. The Contract follows the UUPS (Universal Upgradeable Proxy Standard) Pattern and Implements Secure cross-chain Payment Logic.

Constants and Imports

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.16;
pragma abicoder v2;
import "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol";
import "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol";
import "@openzeppelin/contracts-upgradeable/proxy/utils/UUPSUpgradeable.sol";
import "@routerprotocol/evm-gateway-contracts/contracts/IGateway.sol";

The Contract uses -

  • Solidity Version 0.8.16 or higher for Enhanced Security features.
  • ABI Coder v2 for Improved encoding/decoding Capabilities.
  • OpenZeppelin’s Upgradeable Contracts for Secure Contract Upgradeability.
  • Router Protocol’s Gateway Interface for cross-chain functionality.

Events

event RouterPayRequest(
uint256 requestId, // Unique identifier for the payment request
string dstChainId, // Destination chain identifier
string recipient, // Address of payment recipient
uint256 maxAmount // Maximum amount to be transferred
);
event RouterPayReceive(
string srcChainId, // Source chain identifier
uint256 amount, // Amount being transferred
string recipient // Recipient address
);

These Events Track -

  1. RouterPayRequest - Emitted when Initiating a cross-chain Payment.

    • requestId - Unique Identifier for Tracking.
    • dstChainId - Target Dhain Identifier.
    • recipient - Payment recipient Address.
    • maxAmount - Maximum Transfer Amount.
  2. RouterPayReceive - Emitted when receiving a cross-chain Payment.

    • srcChainId - Origin Chain Identifier.
    • amount - Transferred Amount.
    • recipient - Recipient Address.

State Variables

address public gatewayAddress; // Address of Router Protocol gateway
string public routerChainId; // Router chain identifier
IGateway gateway; // Gateway interface instance
string public routerPayOnRouterChainAddress; // Address of RouterPay on Router chain
  • gatewayAddress - Router Protocol Gateway Contract Address.
  • routerChainId - Chain Identifier for Router Chain.
  • gateway - Interface Instance for Gateway Interactions.
  • routerPayOnRouterChainAddress - Corresponding Contract Address on Router Chain.

Initialization

/**
* @dev Initialize the contract (replaces constructor in upgradeable contracts)
* @param _routerPayOnRouterChainAddress Address of RouterPay contract on Router chain
* @param _routerChainId Router chain identifier
* @param _gatewayAddress Address of Router Protocol gateway
*/
function initialize(
string memory _routerPayOnRouterChainAddress,
string memory _routerChainId,
address payable _gatewayAddress
) public initializer {
__Ownable_init(); // Initialize Ownable contract
__UUPSUpgradeable_init(); // Initialize UUPSUpgradeable contract
routerChainId = _routerChainId;
routerPayOnRouterChainAddress = _routerPayOnRouterChainAddress;
gatewayAddress = _gatewayAddress;
gateway = IGateway(gatewayAddress);
}

This function -

  1. Initializes the Upgradeable Contracts.
  2. Sets the Router Chain Configuration.
  3. Configures the Gateway Contract Interface.

Initiating Payment Withdrawal

/**
* @dev Initiate a cross-chain payment withdrawal
* @param dstChainId Destination chain identifier
* @param recipient Recipient address on destination chain
* @param maxAmount Maximum amount to withdraw
* @param streamId Identifier for the payment stream
* @param requestMetadata Additional metadata for the request
*/
function WithdrawFund(
string memory dstChainId,
string memory recipient,
uint256 maxAmount,
uint64 streamId,
bytes calldata requestMetadata
) public payable {
// Verify destination chain configuration
require(
keccak256(abi.encodePacked(routerPayOnRouterChainAddress)) !=
keccak256(abi.encodePacked("")),
"Invalid dst chain"
);
// Verify request metadata format
require(
uint8(requestMetadata[48]) == 0 &&
keccak256(requestMetadata[50:]) ==
keccak256(abi.encodePacked("")),
"invalid asm or ack"
);
// Prepare payload for cross-chain message
bytes memory payload = abi.encode(
dstChainId,
toBytes(msg.sender),
recipient,
streamId,
maxAmount
);
// Send cross-chain message through gateway
uint256 eventId = gateway.iSend(
1, // Route amount type
0, // Route amount (0 for this case)
"0x", // Route recipient
routerChainId, // Destination chain ID
requestMetadata, // Request metadata
abi.encode(routerPayOnRouterChainAddress, payload) // Encoded payload
);
// Emit event for tracking
emit RouterPayRequest(eventId, dstChainId, recipient, maxAmount);
}

Key Components -

  1. Validation Checks -

    • Destination Chain Configuration.
    • Request Metadata format.
  2. Payload Creation with -

    • Destination Chain ID.
    • Sender Address.
    • Recipient address.
    • Stream ID.
    • Maximum amount.
  3. Cross-chain message sending via gateway.

  4. Event emission for tracking.

Receiving Cross-Chain Payments

/**
* @dev Handle incoming cross-chain payments
* @param requestSender Address of the contract that sent the request
* @param packet Encoded payment data
* @param srcChainId Source chain identifier
* @return Empty string (required by interface)
*/
function iReceive(
string memory requestSender,
bytes memory packet,
string memory srcChainId
) external isGateway returns (string memory) {
// Verify sender is trusted RouterPay contract
require(
keccak256(abi.encodePacked(requestSender)) ==
keccak256(abi.encodePacked(routerPayOnRouterChainAddress)),
"Not called from trusted contract"
);
// Decode payment information
(uint256 amount, string memory recipient) = abi.decode(
packet,
(uint256, string)
);
// Emit event for received payment
emit RouterPayReceive(srcChainId, amount, recipient);
return "";
}

Process -

  1. Verifies sender Authenticity.
  2. Decodes Payment Information.
  3. Emits receipt Event.

Setting DApp Metadata

/**
* @dev Set DApp metadata in the gateway contract
* @param feePayerAddress Address that will pay for cross-chain fees
* @return uint256 Transaction identifier
*/
function setDappMetadata(
string memory feePayerAddress
) external payable onlyOwner returns (uint256) {
return gateway.setDappMetadata{value: msg.value}(feePayerAddress);
}

Allows owner to -

  • Set fee payer address.
  • Configure Gateway Metadata.

Updating Router Pay Contract

/**
* @dev Update the RouterPay contract address on Router chain
* @param _routerPayOnRouterChainAddress New contract address
*/
function updateRouterPayContract(
string memory _routerPayOnRouterChainAddress
) external onlyOwner {
routerPayOnRouterChainAddress = _routerPayOnRouterChainAddress;
}

Enables -

  • Updating Router Chain Contract Address.
  • Maintaining cross-chain Configuration.

Gateway Modifier

/**
* @dev Modifier to ensure only gateway contract can call certain functions
*/
modifier isGateway() {
require(msg.sender == gatewayAddress, "Caller: not gateway");
_;
}

Ensures -

  • Only Gateway can Call cross-chain functions.
  • Protected cross-chain Message handling.

Upgrade Authorization

/**
* @dev Internal function to authorize contract upgrades
* @param newImplementation Address of new implementation contract
*/
function _authorizeUpgrade(
address newImplementation
) internal override onlyOwner {}

Controls -

  • Contract Upgrade Process.
  • Owner-only Upgrade authorization.

Address to Bytes Conversion

/**
* @dev Utility function to convert address to bytes
* @param a Address to convert
* @return b Bytes representation of address
*/
function toBytes(address a) internal pure returns (bytes memory b) {
assembly {
let m := mload(0x40) // Get free memory pointer
// Mask address to ensure it's 20 bytes
a := and(a, 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF)
// Store address with specific encoding
mstore(
add(m, 20),
xor(0x140000000000000000000000000000000000000000, a)
)
mstore(0x40, add(m, 52)) // Update free memory pointer
b := m // Return memory location
}
}

Provides -

  • Efficient address Conversion.
  • Assembly-optimized Processing.

Initiating a Payment Withdrawal

// Example parameters
string memory destinationChain = "polygon";
string memory recipientAddress = "0x...";
uint256 amount = 1000000000000000000; // 1 token with 18 decimals
uint64 streamId = 1;
bytes memory metadata = "0x..."; // Properly formatted metadata
// Call the function
routerPay.WithdrawFund{value: requiredFee}(
destinationChain,
recipientAddress,
amount,
streamId,
metadata
);

Setting Up DApp Configuration

// Set fee payer
string memory feePayerAddress = "0x...";
uint256 requiredFee = 0.1 ether;
routerPay.setDappMetadata{value: requiredFee}(feePayerAddress);