MiniPay Code Library
Snippets of code that can be used to implement flows inside MiniPay
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.
}
}