Smart Contract Interaction

Interact directly with the Paycrest Gateway smart contract for onchain stablecoin-to-fiat (offramp) order creation, settlement, and refunds. This approach gives you full control over the blockchain transactions and is ideal for dapps, wallets, or any EVM-compatible application.

Overview

The Gateway contract is a multi-chain EVM-based smart contract that facilitates the on-chain lifecycle of payment orders. It empowers users to create off-ramp orders while enabling liquidity providers to facilitate those orders at competitive exchange rates.

Prerequisites

  • Ethereum Provider: MetaMask, WalletConnect, or any Web3 provider
  • Ethers.js: For smart contract interactions
  • USDT/USDC Balance: Sufficient token balance for orders
  • Gas Fees: ETH for transaction fees

Connect Wallet

import { ethers } from "ethers";

// Connect to user's wallet
const provider = new ethers.BrowserProvider(window.ethereum);
const signer = await provider.getSigner();
const userAddress = await signer.getAddress();

Initialize Contracts

// Gateway contract configuration
const GATEWAY_ADDRESS = "0xE8bc3B607CfE68F47000E3d200310D49041148Fc";
const USDT_ADDRESS = "0xdac17f958d2ee523a2206206994597c13d831ec7";

// Contract instances
const gateway = new ethers.Contract(GATEWAY_ADDRESS, GATEWAY_ABI, signer);
const usdt = new ethers.Contract(USDT_ADDRESS, USDT_ABI, signer);

Create an Order

async function createOffRampOrder(amount, recipient, refundAddress) {
  try {
    const rate = await getExchangeRate();
    const accountName = await verifyAccount(recipient);
    const messageHash = await encryptRecipientData({ ...recipient, accountName });
    await approveUSDT(amount);
    const tx = await gateway.createOrder(
      USDT_ADDRESS,
      ethers.parseUnits(amount, 6),
      rate,
      ethers.ZeroAddress,
      0,
      refundAddress,
      messageHash
    );
    const receipt = await tx.wait();
    const orderId = extractOrderId(receipt);
    return { orderId, transactionHash: receipt.hash };
  } catch (error) {
    console.error("Error creating order:", error);
    throw error;
  }
}

Exchange Rate and Account Verification

async function getExchangeRate() {
  const response = await fetch("https://api.paycrest.io/v1/rates/usdt/1/ngn");
  const data = await response.json();
  return Number(data.data);
}

async function verifyAccount(recipient) {
  const response = await fetch("https://api.paycrest.io/v1/verify-account", {
    method: "POST",
    headers: { "Content-Type": "application/json" },
    body: JSON.stringify({
      institution: recipient.institution,
      accountIdentifier: recipient.accountIdentifier
    })
  });
  const data = await response.json();
  return data.data;
}

Data Encryption

async function encryptRecipientData(recipient) {
  const response = await fetch("https://api.paycrest.io/v1/pubkey");
  const { data: publicKey } = await response.json();
  const encrypt = new JSEncrypt();
  encrypt.setPublicKey(publicKey);
  const encrypted = encrypt.encrypt(JSON.stringify(recipient));
  if (!encrypted) {
    throw new Error("Failed to encrypt recipient data");
  }
  return encrypted;
}

Token Approval

async function approveUSDT(amount) {
  const usdtAmount = ethers.parseUnits(amount, 6);
  const allowance = await usdt.allowance(userAddress, GATEWAY_ADDRESS);
  if (allowance.lt(usdtAmount)) {
    const tx = await usdt.approve(GATEWAY_ADDRESS, usdtAmount);
    await tx.wait();
  }
}

Get Order Information

async function getOrderInfo(orderId) {
  const orderInfo = await gateway.getOrderInfo(orderId);
  return {
    sender: orderInfo.sender,
    token: orderInfo.token,
    amount: ethers.formatUnits(orderInfo.amount, 6),
    isFulfilled: orderInfo.isFulfilled,
    isRefunded: orderInfo.isRefunded,
    refundAddress: orderInfo.refundAddress
  };
}

Event Listening

gateway.on("OrderCreated", (sender, token, amount, protocolFee, orderId, rate, messageHash) => {
  console.log("Order Created:", {
    sender,
    token,
    amount: ethers.formatUnits(amount, 6),
    orderId,
    rate
  });
});

gateway.on("OrderSettled", (splitOrderId, orderId, liquidityProvider, settlePercent) => {
  console.log("Order Settled:", { orderId, liquidityProvider, settlePercent });
});

gateway.on("OrderRefunded", (fee, orderId) => {
  console.log("Order Refunded:", { 
    fee: ethers.formatUnits(fee, 6), 
    orderId 
  });
});

Error Handling

function handleContractError(error) {
  if (error.code === 4001) {
    throw new Error("User rejected transaction");
  } else if (error.message.includes("insufficient funds")) {
    throw new Error("Insufficient USDT balance or ETH for gas");
  } else if (error.message.includes("execution reverted")) {
    throw new Error("Transaction reverted - check parameters");
  } else {
    throw new Error(`Contract error: ${error.message}`);
  }
}

async function retryTransaction(txFunction, maxRetries = 3) {
  for (let attempt = 1; attempt <= maxRetries; attempt++) {
    try {
      return await txFunction();
    } catch (error) {
      if (attempt === maxRetries) throw error;
      await new Promise(resolve => setTimeout(resolve, 1000 * attempt));
    }
  }
}

Complete Example

class PaycrestGateway {
  constructor(provider, gatewayAddress, usdtAddress) {
    this.provider = provider;
    this.gatewayAddress = gatewayAddress;
    this.usdtAddress = usdtAddress;
    this.gateway = null;
    this.usdt = null;
  }

  async initialize() {
    const signer = await this.provider.getSigner();
    this.gateway = new ethers.Contract(this.gatewayAddress, GATEWAY_ABI, signer);
    this.usdt = new ethers.Contract(this.usdtAddress, USDT_ABI, signer);
    this.setupEventListeners();
  }

  setupEventListeners() {
    this.gateway.on("OrderCreated", this.handleOrderCreated.bind(this));
    this.gateway.on("OrderSettled", this.handleOrderSettled.bind(this));
    this.gateway.on("OrderRefunded", this.handleOrderRefunded.bind(this));
  }

  async createOrder(amount, recipient, refundAddress) {
    try {
      const rate = await this.getExchangeRate();
      const accountName = await this.verifyAccount(recipient);
      const messageHash = await this.encryptRecipientData({ ...recipient, accountName });
      await this.approveUSDT(amount);
      const tx = await this.gateway.createOrder(
        this.usdtAddress,
        ethers.parseUnits(amount, 6),
        rate,
        ethers.ZeroAddress,
        0,
        refundAddress,
        messageHash
      );
      const receipt = await tx.wait();
      return { orderId: this.extractOrderId(receipt), hash: receipt.hash };
    } catch (error) {
      handleContractError(error);
    }
  }
  // ... other methods (getExchangeRate, verifyAccount, etc.)
}

// Usage
const provider = new ethers.BrowserProvider(window.ethereum);
const paycrest = new PaycrestGateway(
  provider,
  "0xE8bc3B607CfE68F47000E3d200310D49041148Fc",
  "0xdac17f958d2ee523a2206206994597c13d831ec7"
);

await paycrest.initialize();

const order = await paycrest.createOrder(
  "100", // USDT amount
  {
    institution: "GTBINGLA",
    accountIdentifier: "123456789"
  },
  "0x123..." // Refund address
);

Supported Networks

  • Base: Primary network for USDT/USDC transactions
  • Polygon: Cost-effective transactions
  • BNB Smart Chain: Binance ecosystem support
  • Arbitrum One: High-performance L2 network
  • Lisk: Alternative blockchain network
  • Celo: Mobile-first blockchain (CUSD, CNGN)
  • Tron: USDT transactions

Paycrest supports very low minimum orders ($0.50) and uses cost-effective EVM L2s. Start with small amounts to test your integration before scaling up.

Choose this method for full onchain control and direct smart contract interaction.

Smart Contract Interaction

Interact directly with the Paycrest Gateway smart contract for onchain stablecoin-to-fiat (offramp) order creation, settlement, and refunds. This approach gives you full control over the blockchain transactions and is ideal for dapps, wallets, or any EVM-compatible application.

Overview

The Gateway contract is a multi-chain EVM-based smart contract that facilitates the on-chain lifecycle of payment orders. It empowers users to create off-ramp orders while enabling liquidity providers to facilitate those orders at competitive exchange rates.

Prerequisites

  • Ethereum Provider: MetaMask, WalletConnect, or any Web3 provider
  • Ethers.js: For smart contract interactions
  • USDT/USDC Balance: Sufficient token balance for orders
  • Gas Fees: ETH for transaction fees

Connect Wallet

import { ethers } from "ethers";

// Connect to user's wallet
const provider = new ethers.BrowserProvider(window.ethereum);
const signer = await provider.getSigner();
const userAddress = await signer.getAddress();

Initialize Contracts

// Gateway contract configuration
const GATEWAY_ADDRESS = "0xE8bc3B607CfE68F47000E3d200310D49041148Fc";
const USDT_ADDRESS = "0xdac17f958d2ee523a2206206994597c13d831ec7";

// Contract instances
const gateway = new ethers.Contract(GATEWAY_ADDRESS, GATEWAY_ABI, signer);
const usdt = new ethers.Contract(USDT_ADDRESS, USDT_ABI, signer);

Create an Order

async function createOffRampOrder(amount, recipient, refundAddress) {
  try {
    const rate = await getExchangeRate();
    const accountName = await verifyAccount(recipient);
    const messageHash = await encryptRecipientData({ ...recipient, accountName });
    await approveUSDT(amount);
    const tx = await gateway.createOrder(
      USDT_ADDRESS,
      ethers.parseUnits(amount, 6),
      rate,
      ethers.ZeroAddress,
      0,
      refundAddress,
      messageHash
    );
    const receipt = await tx.wait();
    const orderId = extractOrderId(receipt);
    return { orderId, transactionHash: receipt.hash };
  } catch (error) {
    console.error("Error creating order:", error);
    throw error;
  }
}

Exchange Rate and Account Verification

async function getExchangeRate() {
  const response = await fetch("https://api.paycrest.io/v1/rates/usdt/1/ngn");
  const data = await response.json();
  return Number(data.data);
}

async function verifyAccount(recipient) {
  const response = await fetch("https://api.paycrest.io/v1/verify-account", {
    method: "POST",
    headers: { "Content-Type": "application/json" },
    body: JSON.stringify({
      institution: recipient.institution,
      accountIdentifier: recipient.accountIdentifier
    })
  });
  const data = await response.json();
  return data.data;
}

Data Encryption

async function encryptRecipientData(recipient) {
  const response = await fetch("https://api.paycrest.io/v1/pubkey");
  const { data: publicKey } = await response.json();
  const encrypt = new JSEncrypt();
  encrypt.setPublicKey(publicKey);
  const encrypted = encrypt.encrypt(JSON.stringify(recipient));
  if (!encrypted) {
    throw new Error("Failed to encrypt recipient data");
  }
  return encrypted;
}

Token Approval

async function approveUSDT(amount) {
  const usdtAmount = ethers.parseUnits(amount, 6);
  const allowance = await usdt.allowance(userAddress, GATEWAY_ADDRESS);
  if (allowance.lt(usdtAmount)) {
    const tx = await usdt.approve(GATEWAY_ADDRESS, usdtAmount);
    await tx.wait();
  }
}

Get Order Information

async function getOrderInfo(orderId) {
  const orderInfo = await gateway.getOrderInfo(orderId);
  return {
    sender: orderInfo.sender,
    token: orderInfo.token,
    amount: ethers.formatUnits(orderInfo.amount, 6),
    isFulfilled: orderInfo.isFulfilled,
    isRefunded: orderInfo.isRefunded,
    refundAddress: orderInfo.refundAddress
  };
}

Event Listening

gateway.on("OrderCreated", (sender, token, amount, protocolFee, orderId, rate, messageHash) => {
  console.log("Order Created:", {
    sender,
    token,
    amount: ethers.formatUnits(amount, 6),
    orderId,
    rate
  });
});

gateway.on("OrderSettled", (splitOrderId, orderId, liquidityProvider, settlePercent) => {
  console.log("Order Settled:", { orderId, liquidityProvider, settlePercent });
});

gateway.on("OrderRefunded", (fee, orderId) => {
  console.log("Order Refunded:", { 
    fee: ethers.formatUnits(fee, 6), 
    orderId 
  });
});

Error Handling

function handleContractError(error) {
  if (error.code === 4001) {
    throw new Error("User rejected transaction");
  } else if (error.message.includes("insufficient funds")) {
    throw new Error("Insufficient USDT balance or ETH for gas");
  } else if (error.message.includes("execution reverted")) {
    throw new Error("Transaction reverted - check parameters");
  } else {
    throw new Error(`Contract error: ${error.message}`);
  }
}

async function retryTransaction(txFunction, maxRetries = 3) {
  for (let attempt = 1; attempt <= maxRetries; attempt++) {
    try {
      return await txFunction();
    } catch (error) {
      if (attempt === maxRetries) throw error;
      await new Promise(resolve => setTimeout(resolve, 1000 * attempt));
    }
  }
}

Complete Example

class PaycrestGateway {
  constructor(provider, gatewayAddress, usdtAddress) {
    this.provider = provider;
    this.gatewayAddress = gatewayAddress;
    this.usdtAddress = usdtAddress;
    this.gateway = null;
    this.usdt = null;
  }

  async initialize() {
    const signer = await this.provider.getSigner();
    this.gateway = new ethers.Contract(this.gatewayAddress, GATEWAY_ABI, signer);
    this.usdt = new ethers.Contract(this.usdtAddress, USDT_ABI, signer);
    this.setupEventListeners();
  }

  setupEventListeners() {
    this.gateway.on("OrderCreated", this.handleOrderCreated.bind(this));
    this.gateway.on("OrderSettled", this.handleOrderSettled.bind(this));
    this.gateway.on("OrderRefunded", this.handleOrderRefunded.bind(this));
  }

  async createOrder(amount, recipient, refundAddress) {
    try {
      const rate = await this.getExchangeRate();
      const accountName = await this.verifyAccount(recipient);
      const messageHash = await this.encryptRecipientData({ ...recipient, accountName });
      await this.approveUSDT(amount);
      const tx = await this.gateway.createOrder(
        this.usdtAddress,
        ethers.parseUnits(amount, 6),
        rate,
        ethers.ZeroAddress,
        0,
        refundAddress,
        messageHash
      );
      const receipt = await tx.wait();
      return { orderId: this.extractOrderId(receipt), hash: receipt.hash };
    } catch (error) {
      handleContractError(error);
    }
  }
  // ... other methods (getExchangeRate, verifyAccount, etc.)
}

// Usage
const provider = new ethers.BrowserProvider(window.ethereum);
const paycrest = new PaycrestGateway(
  provider,
  "0xE8bc3B607CfE68F47000E3d200310D49041148Fc",
  "0xdac17f958d2ee523a2206206994597c13d831ec7"
);

await paycrest.initialize();

const order = await paycrest.createOrder(
  "100", // USDT amount
  {
    institution: "GTBINGLA",
    accountIdentifier: "123456789"
  },
  "0x123..." // Refund address
);

Supported Networks

  • Base: Primary network for USDT/USDC transactions
  • Polygon: Cost-effective transactions
  • BNB Smart Chain: Binance ecosystem support
  • Arbitrum One: High-performance L2 network
  • Lisk: Alternative blockchain network
  • Celo: Mobile-first blockchain (CUSD, CNGN)
  • Tron: USDT transactions

Paycrest supports very low minimum orders ($0.50) and uses cost-effective EVM L2s. Start with small amounts to test your integration before scaling up.

Choose this method for full onchain control and direct smart contract interaction.