Passport Beta

How To Get Your Smart Contract to Receive Data from Umbrella Network’s Passport?

Passport Beta is a solution that allows your dApp to receive Layer-2 Data directly. For this, the Passport Beta is responsible for fetching Layer-2 Data from Umbrella's API, subimtting it into the blockchain, verifying the root hash and injecting the Layer-2 Data into the smart contract.

Passport Beta allows the dApp to define the logic under which conditions the Layer-2 Data must be delivered to the smart contract.

Preparations

First, You'll need a smart contract that implements the IDatumReceiver interface to be able to receive data from the Passport. Instructions for each method are included as comments in the code.
The interface and the base contracts you have to implement are available in our Toolbox version 5.7.0 or higher.

Follows the code for the IDatumReceiver interface:

//SPDX-License-Identifier: MIT
pragma solidity ^0.7.6;
pragma abicoder v2;

import "../lib/PassportStructs.sol";

interface IDatumReceiver {
  /// @notice This function will hold the parameters or business rules that consumer
  /// wants to do with the received data structure, here called Pallet.
  /// @param _pallet the structure sent by DatumRegistry, containing proof, key and value
  function receivePallet(Pallet calldata _pallet) external;

  /// @notice This function holds rules that consumer may need to check before accepting
  /// the Pallet. Rules like how old is the block, or how many blocks have passed since 
  /// last storage. Deliverer will check if approvePallet reverted this call or returned true.
  /// @param _pallet The exact same Pallet that will arrive at the receivePallet endpoint.
  /// @return true if wants pallet or should REVERT if Contract does not want the pallet.
  /// @dev DO NOT RETURN false.
  function approvePallet(Pallet calldata _pallet) external view returns (bool);
}

🚧

Pay special attention on the rules defined on approvePallet. If receivePallet execution reverts, fees will be incurred.

📘

Note

Required data structures are included with the interface.

// SPDX-License-Identifier: MIT
pragma solidity ^0.7.6;

/// @notice Holds the information of the Contract interested on receiving data
/// @param receiver The address of the receiver contract
/// @param keys The array of bytes32 encoded keys. Encode with Umbrella's Toolbox to match the correct length.
/// @param funder The address of the wallet that will be allowed to manage funds of this Datum.
/// @param balance The balance in UMB this Datum holds.
/// @param enabled True if the Datum is enabled and eligible to receive data, false if owner doesn't want data.
struct Datum {
  address receiver;
  bytes32[] keys;
  address funder;
  // total supply of UMB can be saved using 89bits, so we good with 128
  uint128 balance;
  bool enabled;
}

/// @notice Holds the information pertinent to that piece of data.
/// @param blockId Umbrella Network sidechain's blockId.
/// @param key Key encoded in bytes32
/// @param value Value encoded in bytes32
/// @param proof Merkle proof to verify that the data was really minted by Umbrella's sidechain. 
struct Pallet {
  uint32 blockId;
  bytes32 key;
  bytes32 value;
  bytes32[] proof; 
}

/// @notice Holds the relation between Datum and the data it's interested on.
/// @param datumId The keccack256 hash that indexes the Datum the delivery goes to.
/// @param indexes With a Pallet[], represents the position on the array that the Pallets this Datum wants is.  
struct Delivery {
  bytes32 datumId;
  uint256[] indexes;
}

We built a reference app called "Standard Datum Receiver" which implements IDatumReceiver interface and adds some business rules to store the latest received key-value pairs. Those business rules can be changed accordingly to the customer's needs.
You can check the "Standard Datum Receiver" contract on BSC Mainnet at the following address: 0xd3e5Bf479BF8A2252D89D2990dDE2173869166D0.

Note: On the "Standard Datum Receiver", a check on approvePallet function was implemented NOT to approve pallets on every delivery round.

To avoid receivePallet method being invoked by a smart contracts other than the Umbrella's Passport, the receivePallet must check the address of the Sender. In order to do this, you can use the following example:
https://github.com/umbrella-network/babel/blob/main/contracts/Passport/StandardDatumReceiver.sol#L38

👍

To get the DatumRegistry address resolved, the ContractRegistry must be used to ensure you dynamically retrieve the address.

Interacting with Datum Registry Contract

After the receiver smart contract is created, it needs to be registered on the Umbrella's DatumRegistry smart contract.

📘

DatumRegistry smart contract address should be obtained through Umbrella's Contract Registry smart contract.

In order to do so, getAddressByString function must be called on Umbrella's Contract Registry using the DatumRegistry value as a parameter.

Type UMB and grab the Token Contract address also.

🚧

Before executing these scripts, pay attention to the dependencies you'll need to install.

Creating a Datum

Before creating the Datum, make sure you approved the DatumRegistry contract to spend your UMB tokens. To do that, simply go to the Token Contract address and call the approve method being the spender the DatumRegistry Address and the amount the number of UMB tokens you are willing to allow the contract to spend on your behalf (take into consideration UMB has 18 decimals). You will be able to deposit less than the allowed and withdraw them at any time unless they were already used for delivering data.

To exemplify how to create a Datum, we are sharing below TypeScript code:

require('dotenv').config(); //eslint-disable-line

import {Contract} from '@ethersproject/contracts';
import {ethers} from 'ethers';
import {LeafKeyCoder, ABI} from '@umb-network/toolbox';

const {DATUM_REGISTRY_ADDRESS, DATUM_RECEIVER_ADDRESS, PRIVATE_KEY, BSC_RPC_SERVER} = process.env;

async function main() {
  const provider = ethers.getDefaultProvider(BSC_RPC_SERVER);
  const wallet = new ethers.Wallet(PRIVATE_KEY as string, provider);

  console.log('Signer is:', wallet.address);

  const createParams = {
    datumReceiver: DATUM_RECEIVER_ADDRESS,
    funder: wallet.address,
    encodedKeys: [
      `0x${LeafKeyCoder.encode('ETH-USD').toString('hex')}`,
      `0x${LeafKeyCoder.encode('BTC-USD').toString('hex')}`,
    ],
    depositAmount: '100' + '0'.repeat(18),
  };

  console.log('Creating with params', createParams);
  const datumRegistry = new Contract(DATUM_REGISTRY_ADDRESS as string, ABI.datumRegistryAbi, provider);

  const result = await datumRegistry
    .connect(wallet)
    .create(createParams.datumReceiver, createParams.funder, createParams.encodedKeys, createParams.depositAmount);

  console.log(result);
}

main();

After executing above code, the Datum is created. You may confirm this by checking on both the DatumRegistry transactions and the Passport API.

That's it! Your smart contract will be able to receive configured keys provided your Datum is funded and conditions defined on the receivePallet are met.

Disabling Datum

f you wish to turn off your Datum temporarily, you must SET the ENABLED parameter to false . This will prevent Passport from calling your approvePallet and acceptPallet methods.

📘

Withdrawing all your funds will automatically set the ENABLED parameter to false.

require('dotenv').config(); //eslint-disable-line

import {Contract} from '@ethersproject/contracts';
import {ABI} from '@umb-network/toolbox';
import {ethers} from 'ethers';

const {DATUM_REGISTRY_ADDRESS, PRIVATE_KEY, BSC_RPC_SERVER} = process.env;

async function main(operation: 'enable' | 'disable', receiverAddress: string) {
  const provider = ethers.getDefaultProvider(BSC_RPC_SERVER);
  const wallet = new ethers.Wallet(PRIVATE_KEY as string, provider);

  console.log('Signer is:', wallet.address);
  const datumRegistry = new Contract(DATUM_REGISTRY_ADDRESS as string, ABI.datumRegistryAbi, provider);

  const enabledFlag = operation === 'enable' ? 1 : 0;

  const result = await datumRegistry.connect(wallet).setDatumEnabled(receiverAddress, enabledFlag);

  console.log(result);
  result.wait();
}

(async () => {
  await main('enable', '0xA');
})();

Managing Datum funds

It is important to keep your Datum funded or it'll stop receiving updates. This is done by approving the maximum number of $UMB tokens that can be held on your Datum (allowance) and by depositing $UMB tokens to your Datum.
With every delivery of data, $UMB will be taken from your Datum to cover the cost of the transaction. Hence keeping an eye on your Datum balance is important.

require('dotenv').config(); //eslint-disable-line

import {Contract} from '@ethersproject/contracts';
import {ABI} from '@umb-network/toolbox';
import {ethers} from 'ethers';

const {DATUM_REGISTRY_ADDRESS, PRIVATE_KEY, BSC_RPC_SERVER} = process.env;

async function main(operation: 'deposit' | 'withdraw', receiverAddress: string) {
  const provider = ethers.getDefaultProvider(BSC_RPC_SERVER);
  const wallet = new ethers.Wallet(PRIVATE_KEY as string, provider);

  console.log('Signer is:', wallet.address);
  const datumRegistry = new Contract(DATUM_REGISTRY_ADDRESS as string, ABI.datumRegistryAbi, provider);

  const getBalance = async () => {
    return (
      await datumRegistry.datums(await datumRegistry.resolveId(receiverAddress, wallet.address))
    ).balance.toString();
  };

  const balance = await getBalance();
  console.log('Current balance:', balance);

  if (operation === 'deposit') {
    const depositParams = {
      receiverAddress,
      depositAmount: '500' + '0'.repeat(18),
    };
    console.log('Deposit Params', depositParams);

    const result = await datumRegistry
      .connect(wallet)
      .deposit(depositParams.receiverAddress, depositParams.depositAmount);

    console.log(result);
  }

  if (operation === 'withdraw') {
    const result = await datumRegistry.connect(wallet).withdraw(receiverAddress, balance);

    console.log(result);
  }
}

(async () => {
  await main('deposit', '0xA');
})();

How to update the Datum keys

If you want to add or remove keys from your Datum, use the following script. Pay attention that you can add that key twice on your Datum. You probably don't want that. You can check the Datums you created and how they're configured with the following query filter: https://passport-api.umb.network/datums?funder=<0xYourWalletAddress>.

require('dotenv').config(); //eslint-disable-line

import {LeafKeyCoder, ABI} from '@umb-network/toolbox';
import {Contract} from '@ethersproject/contracts';
import {ethers} from 'ethers';

const {DATUM_REGISTRY_ADDRESS, PRIVATE_KEY, BSC_RPC_SERVER} = process.env;

async function main(operation: 'add' | 'remove', receiverAddress: string, keyPairs: string[]) {
  const provider = ethers.getDefaultProvider(BSC_RPC_SERVER);
  const wallet = new ethers.Wallet(PRIVATE_KEY as string, provider);

  console.log('Signer is:', wallet.address);
  const datumRegistry = new Contract(DATUM_REGISTRY_ADDRESS as string, ABI.datumRegistryAbi, provider);

  const encodedKeys = keyPairs.map(keyPair => `0x${LeafKeyCoder.encode(keyPair).toString('hex')}`);
  console.log('Key:', encodedKeys);

  if (operation === 'add') {
    const result = await datumRegistry.connect(wallet).addKeys(receiverAddress, encodedKeys);
    console.log(result);
    await result.wait();
  }

  if (operation === 'remove') {
    const result = await datumRegistry.connect(wallet).removeKeys(receiverAddress, encodedKeys);
    console.log(result);
    await result.wait();
  }
}

(async () => {
  await main('add', '0xA', ['BNB-USD']);
})();

Passport Beta is running only on BSC at the moment.

Should you have doubts or require further assistance, feel free to join our Discord Channel and contact the team. https://discord.gg/sYf7SKsv