MiniPay Code Library
Snippets of code that can be used to implement flows inside MiniPay
warning
Make sure you are using Typescript v5 or above and Viem v2 or above.
Get the connected user's address without any Library
// The code must run in a browser environment and not in node environment
if (window && window.ethereum) {
// User has a injected wallet
if (window.ethereum.isMiniPay) {
// User is using Minipay
// Requesting account addresses
let accounts = await window.ethereum.request({
method: "eth_requestAccounts",
params: [],
});
// Injected wallets inject all available addresses,
// to comply with API Minipay injects one address but in the form of array
console.log(accounts[0]);
}
// User is not using MiniPay
}
// User does not have a injected wallet
To use the code snippets below, install the following packages:
- yarn
- npm
yarn add @celo/abis @celo/identity viem@1
npm install @celo/abis @celo/identity viem@1
Check cUSD Balance of an address
const { getContract, formatEther, createPublicClient, http } = require("viem");
const { celo } = require("viem/chains");
const { stableTokenABI } = require("@celo/abis");
const STABLE_TOKEN_ADDRESS = "0x765DE816845861e75A25fCA122bb6898B8B1282a";
async function checkCUSDBalance(publicClient, address) {
let StableTokenContract = getContract({
abi: stableTokenABI,
address: STABLE_TOKEN_ADDRESS,
publicClient,
});
let balanceInBigNumber = await StableTokenContract.read.balanceOf([
address,
]);
let balanceInWei = balanceInBigNumber.toString();
let balanceInEthers = formatEther(balanceInWei);
return balanceInEthers;
}
const publicClient = createPublicClient({
chain: celo,
transport: http(),
}); // Mainnet
let balance = await checkCUSDBalance(publicClient, address); // In Ether unit
Check If a transaction succeeded
const { createPublicClient, http } = require("viem");
const { celo } = require("viem/chains");
async function checkIfTransactionSucceeded(publicClient, transactionHash) {
let receipt = await publicClient.getTransactionReceipt({
hash: transactionHash,
});
return receipt.status === "success";
}
const publicClient = createPublicClient({
chain: celo,
transport: http(),
}); // Mainnet
let transactionStatus = await checkIfTransactionSucceeded(
publicClient,
transactionHash
);
Estimate Gas for a transaction (in Celo)
const { createPublicClient, http } = require("viem");
const { celo } = require("viem/chains");
async function estimateGas(publicClient, transaction, feeCurrency = "") {
return await publicClient.estimateGas({
...transaction,
feeCurrency: feeCurrency ? feeCurrency : "",
});
}
const publicClient = createPublicClient({
chain: celo,
transport: http(),
});
let gasLimit = await estimateGas(publicClient, {
account: "0x8eb02597d85abc268bc4769e06a0d4cc603ab05f",
to: "0x4f93fa058b03953c851efaa2e4fc5c34afdfab84",
value: "0x1",
data: "0x",
});
Estimate Gas for a transaction (in cUSD)
const { createPublicClient, http } = require("viem");
const { celo } = require("viem/chains");
async function estimateGas(publicClient, transaction, feeCurrency = "") {
return await publicClient.estimateGas({
...transaction,
feeCurrency: feeCurrency ? feeCurrency : "",
});
}
const publicClient = createPublicClient({
chain: celo,
transport: http(),
});
const STABLE_TOKEN_ADDRESS = "0x765DE816845861e75A25fCA122bb6898B8B1282a";
let gasLimit = await estimateGas(
publicClient,
{
account: "0x8eb02597d85abc268bc4769e06a0d4cc603ab05f",
to: "0x4f93fa058b03953c851efaa2e4fc5c34afdfab84",
value: "0x1",
data: "0x",
},
STABLE_TOKEN_ADDRESS
);
Estimate Gas Price for a transaction (in Celo)
const { createPublicClient, http } = require("viem");
const { celo } = require("viem/chains");
async function estimateGasPrice(publicClient, feeCurrency = "") {
return await publicClient.request({
method: "eth_gasPrice",
params: feeCurrency ? [feeCurrency] : [],
});
}
const publicClient = createPublicClient({
chain: celo,
transport: http(),
});
let gasPrice = await estimateGasPrice(publicClient);
Estimate Gas Price for a transaction (in cUSD)
const { createPublicClient, http } = require("viem");
const { celo } = require("viem/chains");
async function estimateGasPrice(publicClient, feeCurrency = "") {
return await publicClient.request({
method: "eth_gasPrice",
params: feeCurrency ? [feeCurrency] : [],
});
}
const publicClient = createPublicClient({
chain: celo,
transport: http(),
});
const STABLE_TOKEN_ADDRESS = "0x765DE816845861e75A25fCA122bb6898B8B1282a";
let gasPrice = await estimateGasPrice(publicClient, STABLE_TOKEN_ADDRESS);
Calculate cUSD to be spent for transaction fees
const { createPublicClient, http, formatEther } = require("viem");
const { celo } = require("viem/chains");
const publicClient = createPublicClient({
chain: celo,
transport: http(),
});
const STABLE_TOKEN_ADDRESS = "0x765DE816845861e75A25fCA122bb6898B8B1282a";
// `estimateGas` implemented above
let gasLimit = await estimateGas(
publicClient,
{
account: "0x8eb02597d85abc268bc4769e06a0d4cc603ab05f",
to: "0x4f93fa058b03953c851efaa2e4fc5c34afdfab84",
value: "0x1",
data: "0x",
},
STABLE_TOKEN_ADDRESS
);
// `estimateGasPrice` implemented above
let gasPrice = await estimateGasPrice(publicClient, STABLE_TOKEN_ADDRESS);
let transactionFeesInCUSD = formatEther(gasLimit * hexToBigInt(gasPrice));
Resolve Minipay phone numbers to Addresses
- index.js
- SocialConnect.js
const { createPublicClient, http } = require("viem");
const { celo } = require("viem/chains");
const { privateKeyToAccount } = require("viem/accounts");
const { SocialConnectIssuer } = require("./SocialConnect.js");
let account = privateKeyToAccount(process.env.ISSUER_PRIVATE_KEY);
let walletClient = createWalletClient({
account,
transport: http(),
chain,
});
const issuer = new SocialConnectIssuer(walletClient, {
authenticationMethod: AuthenticationMethod.ENCRYPTION_KEY,
rawKey: process.env.DEK_PRIVATE_KEY,
});
await issuer.initialize();
const identifierType = IdentifierPrefix.PHONE_NUMBER;
/**
* Any phone number you want to lookup
*
* The below phone number is registered on the testnet issuer mentioned below.
*/
const identifier = "+911234567890";
/**
* You can lookup under multiple issuers in one request.
*
* Below is the MiniPay issuer address on Mainnet.
*
* Note: Remember to make your environment variable ENVIRONMENT=MAINNET
*/
let issuerAddresses = ["0x7888612486844Bb9BE598668081c59A9f7367FBc"];
// A testnet issuer we setup for you to lookup on testnet.
// let issuerAddresses = ["0xDF7d8B197EB130cF68809730b0D41999A830c4d7"];
let results = await issuer.lookup(identifier, identifierType, issuerAddresses);
const {
federatedAttestationsABI,
odisPaymentsABI,
stableTokenABI,
} = require("@celo/abis");
const { getContract } = require("viem");
const { OdisUtils } = require("@celo/identity");
const { OdisContextName } = require("@celo/identity/lib/odis/query");
const ONE_CENT_CUSD = parseEther("0.01");
const SERVICE_CONTEXT =
process.env.ENVIRONMENT === "TESTNET"
? OdisContextName.ALFAJORES
: OdisContextName.MAINNET;
class SocialConnectIssuer {
walletClient;
authSigner;
federatedAttestationsContractAddress;
federatedAttestationsContract;
odisPaymentsContractAddress;
odisPaymentsContract;
stableTokenContractAddress;
stableTokenContract;
serviceContext;
initialized = false;
constructor(walletClient, authSigner) {
this.walletClient = walletClient;
this.authSigner = authSigner;
this.serviceContext =
OdisUtils.Query.getServiceContext(SERVICE_CONTEXT);
}
async initialize() {
this.federatedAttestationsContractAddress =
await getCoreContractAddress("FederatedAttestations");
this.federatedAttestationsContract = getContract({
address: this.federatedAttestationsContractAddress,
abi: federatedAttestationsABI,
// Needed for lookup
publicClient,
// Needed for registeration and de-registration
walletClient: this.walletClient,
});
this.odisPaymentsContractAddress = await getCoreContractAddress(
"OdisPayments"
);
this.odisPaymentsContract = getContract({
address: this.odisPaymentsContractAddress,
abi: odisPaymentsABI,
walletClient: this.walletClient,
});
this.stableTokenContractAddress = await getCoreContractAddress(
"StableToken"
);
this.stableTokenContract = getContract({
address: this.stableTokenContractAddress,
abi: stableTokenABI,
walletClient: this.walletClient,
});
this.initialized = true;
}
async #getObfuscatedId(plaintextId, identifierType) {
// TODO look into client side blinding
const { obfuscatedIdentifier } =
await OdisUtils.Identifier.getObfuscatedIdentifier(
plaintextId,
identifierType,
this.walletClient.account.address,
this.authSigner,
this.serviceContext
);
return obfuscatedIdentifier;
}
async #checkAndTopUpODISQuota() {
const remainingQuota = await this.checkODISQuota();
if (remainingQuota < 1) {
// TODO make threshold a constant
let approvalTxHash =
await this.stableTokenContract.write.increaseAllowance([
this.odisPaymentsContractAddress,
ONE_CENT_CUSD, // TODO we should increase by more
]);
let approvalTxReceipt =
await publicClient.waitForTransactionReceipt({
hash: approvalTxHash,
});
let odisPaymentTxHash =
await this.odisPaymentsContract.write.payInCUSD([
this.walletClient.account,
ONE_CENT_CUSD, // TODO we should increase by more
]);
let odisPaymentReceipt =
await publicClient.waitForTransactionReceipt({
hash: odisPaymentTxHash,
});
}
}
async getObfuscatedIdWithQuotaRetry(plaintextId, identifierType) {
if (this.initialized) {
try {
return await this.#getObfuscatedId(plaintextId, identifierType);
} catch {
await this.#checkAndTopUpODISQuota();
return this.#getObfuscatedId(plaintextId, identifierType);
}
}
throw new Error("SocialConnect instance not initialized");
}
async registerOnChainIdentifier(plaintextId, identifierType, address) {
if (this.initialized) {
const obfuscatedId = await this.getObfuscatedIdWithQuotaRetry(
plaintextId,
identifierType
);
const hash =
await this.federatedAttestationsContract.write.registerAttestationAsIssuer(
[
// TODO check if there are better code patterns for sending txs
obfuscatedId,
address,
NOW_TIMESTAMP,
]
);
const receipt = await publicClient.waitForTransactionReceipt({
hash,
});
return receipt;
}
throw new Error("SocialConnect instance not initialized");
}
async deregisterOnChainIdentifier(plaintextId, identifierType, address) {
if (this.initialized) {
const obfuscatedId = await this.getObfuscatedIdWithQuotaRetry(
plaintextId,
identifierType
);
const hash =
await this.federatedAttestationsContract.write.revokeAttestation(
[obfuscatedId, this.walletClient.account.address, address]
);
const receipt = await publicClient.waitForTransactionReceipt({
hash,
});
return receipt;
}
throw new Error("SocialConnect instance not initialized");
}
async checkODISQuota() {
if (this.initialized) {
const { remainingQuota } = await OdisUtils.Quota.getPnpQuotaStatus(
this.walletClient.account.address,
this.authSigner,
this.serviceContext
);
console.log("Remaining Quota", remainingQuota);
return remainingQuota;
}
throw new Error("SocialConnect instance not initialized");
}
async lookup(plaintextId, identifierType, issuerAddresses) {
if (this.initialized) {
const obfuscatedId = await this.getObfuscatedIdWithQuotaRetry(
plaintextId,
identifierType
);
const attestations =
await this.federatedAttestationsContract.read.lookupAttestations(
[obfuscatedId, issuerAddresses]
);
return {
accounts: attestations[1], // Viem returns data as is from contract not in JSON
obfuscatedId,
};
}
throw new Error("SocialConnect instance not initialized");
}
}
Request an ERC20 token transfer
import { createWalletClient, custom } from 'viem'
// import { celo } from 'viem/chains'
import { celoAlfajores } from 'viem/chains'
const client = createWalletClient({
chain: celoAlfajores,
// chain: celo,
transport: custom(window.ethereum!)
})
const publicClient = createPublicClient({
chain: celoAlfajores,
// chain: celo,
transport: http()
})
async function requestTransfer(tokenAddress, transferValue, tokenDecimals) {
let hash = await client.sendTransaction({
to: tokenAddress,
// to: '0x765DE816845861e75A25fCA122bb6898B8B1282a' // cUSD (Mainnet)
// to: '0xcebA9300f2b948710d2653dD7B07f33A8B32118C' // USDC (Mainnet)
// to: '0x48065fbbe25f71c9282ddf5e1cd6d6a887483d5e' // USDT (Mainnet)
data: encodeFunctionData({
abi: stableTokenAbi, // Token ABI can be fetched from Explorer.
functionName: "transfer",
args: [
receiverAddress,
// Different tokens can have different decimals, cUSD (18), USDC (6)
parseUnits(`${Number(transferValue)}`, tokenDecimals),
],
}),
// If the wallet is connected to a different network then you get an error.
chain: celoAlfajores,
// chain: celo,
});
const transaction = await publicClient.waitForTransactionReceipt({
hash, // Transaction hash that can be used to search transaction on the explorer.
});
if (transaction.status === "success") {
// Do something after transaction is successful.
} else {
// Do something after transaction has failed.
}
}