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.
This guide uses Viem for smart contract interactions, which is the recommended Ethereum library for modern applications. Viem provides better TypeScript support, improved performance, and a more intuitive API compared to ethers.js.
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
- Viem: For smart contract interactions
- USDT/USDC Balance: Sufficient token balance for orders
- Gas Fees: ETH for transaction fees
Connect Wallet
JavaScript
Python
Go
cURL
import { createPublicClient, createWalletClient, http, getAddress } from "viem";
import { base } from "viem/chains";
// Connect to user's wallet
const publicClient = createPublicClient({
chain: base,
transport: http()
});
const walletClient = await createWalletClient({
chain: base,
transport: window.ethereum
});
const [userAddress] = await walletClient.getAddresses();
Initialize Contracts
JavaScript
Python
Go
cURL
import { getContract } from "viem";
// Gateway contract configuration
const GATEWAY_ADDRESS = "0xE8bc3B607CfE68F47000E3d200310D49041148Fc";
const USDT_ADDRESS = "0xdac17f958d2ee523a2206206994597c13d831ec7";
// Contract instances
const gateway = getContract({
address: GATEWAY_ADDRESS,
abi: GATEWAY_ABI,
publicClient,
walletClient
});
const usdt = getContract({
address: USDT_ADDRESS,
abi: USDT_ABI,
publicClient,
walletClient
});
Create an Order
JavaScript
Python
Go
cURL
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 { request } = await gateway.simulate.createOrder({
args: [
USDT_ADDRESS,
parseUnits(amount, 6),
rate,
zeroAddress,
0n,
refundAddress,
messageHash
]
});
const hash = await walletClient.writeContract(request);
const receipt = await publicClient.waitForTransactionReceipt({ hash });
const orderId = extractOrderId(receipt);
return { orderId, transactionHash: receipt.hash };
} catch (error) {
console.error("Error creating order:", error);
throw error;
}
}
Exchange Rate and Account Verification
JavaScript
Python
Go
cURL
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
JavaScript
Python
Go
cURL
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
JavaScript
Python
Go
cURL
async function approveUSDT(amount) {
const usdtAmount = parseUnits(amount, 6);
const allowance = await usdt.read.allowance([userAddress, GATEWAY_ADDRESS]);
if (allowance < usdtAmount) {
const { request } = await usdt.simulate.approve({
args: [GATEWAY_ADDRESS, usdtAmount]
});
const hash = await walletClient.writeContract(request);
await publicClient.waitForTransactionReceipt({ hash });
}
}
JavaScript
Python
Go
cURL
async function getOrderInfo(orderId) {
const orderInfo = await gateway.read.getOrderInfo([orderId]);
return {
sender: orderInfo.sender,
token: orderInfo.token,
amount: formatUnits(orderInfo.amount, 6),
isFulfilled: orderInfo.isFulfilled,
isRefunded: orderInfo.isRefunded,
refundAddress: orderInfo.refundAddress
};
}
Event Listening
JavaScript
Python
Go
cURL
// Listen for events using viem's watchContractEvent
const unwatchOrderCreated = publicClient.watchContractEvent({
address: GATEWAY_ADDRESS,
abi: GATEWAY_ABI,
eventName: 'OrderCreated',
onLogs: (logs) => {
logs.forEach((log) => {
console.log("Order Created:", {
sender: log.args.sender,
token: log.args.token,
amount: formatUnits(log.args.amount, 6),
orderId: log.args.orderId,
rate: log.args.rate
});
});
}
});
const unwatchOrderSettled = publicClient.watchContractEvent({
address: GATEWAY_ADDRESS,
abi: GATEWAY_ABI,
eventName: 'OrderSettled',
onLogs: (logs) => {
logs.forEach((log) => {
console.log("Order Settled:", {
orderId: log.args.orderId,
liquidityProvider: log.args.liquidityProvider,
settlePercent: log.args.settlePercent
});
});
}
});
const unwatchOrderRefunded = publicClient.watchContractEvent({
address: GATEWAY_ADDRESS,
abi: GATEWAY_ABI,
eventName: 'OrderRefunded',
onLogs: (logs) => {
logs.forEach((log) => {
console.log("Order Refunded:", {
fee: formatUnits(log.args.fee, 6),
orderId: log.args.orderId
});
});
}
});
Error Handling
JavaScript
Python
Go
cURL
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
JavaScript
Python
Go
cURL
import { parseUnits, formatUnits, zeroAddress } from "viem";
class PaycrestGateway {
constructor(publicClient, walletClient, gatewayAddress, usdtAddress) {
this.provider = publicClient;
this.walletClient = walletClient;
this.gatewayAddress = gatewayAddress;
this.usdtAddress = usdtAddress;
this.gateway = null;
this.usdt = null;
}
async initialize() {
this.gateway = getContract({
address: this.gatewayAddress,
abi: GATEWAY_ABI,
publicClient: this.provider,
walletClient: this.walletClient
});
this.usdt = getContract({
address: this.usdtAddress,
abi: USDT_ABI,
publicClient: this.provider,
walletClient: this.walletClient
});
this.setupEventListeners();
}
setupEventListeners() {
this.provider.watchContractEvent({
address: this.gatewayAddress,
abi: GATEWAY_ABI,
eventName: 'OrderCreated',
onLogs: this.handleOrderCreated.bind(this)
});
this.provider.watchContractEvent({
address: this.gatewayAddress,
abi: GATEWAY_ABI,
eventName: 'OrderSettled',
onLogs: this.handleOrderSettled.bind(this)
});
this.provider.watchContractEvent({
address: this.gatewayAddress,
abi: GATEWAY_ABI,
eventName: 'OrderRefunded',
onLogs: 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 { request } = await this.gateway.simulate.createOrder({
args: [
this.usdtAddress,
parseUnits(amount, 6),
rate,
zeroAddress,
0n,
refundAddress,
messageHash
]
});
const hash = await this.walletClient.writeContract(request);
const receipt = await this.provider.waitForTransactionReceipt({ hash });
return { orderId: this.extractOrderId(receipt), hash: receipt.hash };
} catch (error) {
handleContractError(error);
}
}
// ... other methods (getExchangeRate, verifyAccount, etc.)
}
// Usage
const paycrest = new PaycrestGateway(
publicClient,
walletClient,
"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.
Failed transactions still consume gas. Validate all parameters before submitting transactions.
Choose this method for full onchain control and direct smart contract interaction.