Make sure you are using Typescript v5 or above and Viem v2 or above.
Get the connected user’s address without any Library
Copy
Ask AI
// 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
Copy
Ask AI
yarn add @celo/abis @celo/identity viem@2
Check cUSD Balance of an address
Copy
Ask AI
import { getContract, formatEther, createPublicClient, http } from "viem";
import { celo } from "viem/chains";
import { stableTokenABI } from "@celo/abis";
// cUSD address on Celo mainnet
const STABLE_TOKEN_ADDRESS = "0x765DE816845861e75A25fCA122bb6898B8B1282a";
async function checkCUSDBalance(publicClient, address) {
const StableTokenContract = getContract({
abi: stableTokenABI,
address: STABLE_TOKEN_ADDRESS,
client: publicClient,
});
const balanceInBigNumber = await StableTokenContract.read.balanceOf([
address,
]);
const balanceInWei = balanceInBigNumber.toString();
const balanceInEthers = formatEther(balanceInWei);
return balanceInEthers;
}
const publicClient = createPublicClient({
chain: celo,
transport: http(),
}); // Mainnet
const balance = await checkCUSDBalance(publicClient, address); // In Ether unit
Check If a transaction succeeded
Copy
Ask AI
import { createPublicClient, http } from "viem";
import { celo } from "viem/chains";
async function checkIfTransactionSucceeded(publicClient, transactionHash) {
const receipt = await publicClient.getTransactionReceipt({
hash: transactionHash,
});
return receipt.status === "success";
}
const publicClient = createPublicClient({
chain: celo,
transport: http(),
}); // Mainnet
const transactionStatus = await checkIfTransactionSucceeded(
publicClient,
transactionHash
);
Estimate Gas for a transaction (in Celo)
Copy
Ask AI
import { createPublicClient, http } from "viem";
import { celo } from "viem/chains";
async function estimateGas(publicClient, transaction, feeCurrency = "") {
return await publicClient.estimateGas({
...transaction,
feeCurrency: feeCurrency ? feeCurrency : "",
});
}
const publicClient = createPublicClient({
chain: celo,
transport: http(),
});
const gasLimit = await estimateGas(publicClient, {
account: "0x8eb02597d85abc268bc4769e06a0d4cc603ab05f",
to: "0x4f93fa058b03953c851efaa2e4fc5c34afdfab84",
value: "0x1",
data: "0x",
});
Estimate Gas for a transaction (in cUSD)
Copy
Ask AI
import { createPublicClient, http } from "viem";
import { celo } from "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";
const gasLimit = await estimateGas(
publicClient,
{
account: "0x8eb02597d85abc268bc4769e06a0d4cc603ab05f",
to: "0x4f93fa058b03953c851efaa2e4fc5c34afdfab84",
value: "0x1",
data: "0x",
},
STABLE_TOKEN_ADDRESS
);
Estimate Gas Price for a transaction (in Celo)
Copy
Ask AI
import { createPublicClient, http } from "viem";
import { celo } from "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 gasPrice = await estimateGasPrice(publicClient);
Estimate Gas Price for a transaction (in cUSD)
Copy
Ask AI
import { createPublicClient, http } from "viem";
import { celo } from "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";
const gasPrice = await estimateGasPrice(publicClient, STABLE_TOKEN_ADDRESS);
Calculate cUSD to be spent for transaction fees
Copy
Ask AI
import { createPublicClient, http, formatEther, fromHex } from "viem";
import { celo } from "viem/chains";
const publicClient = createPublicClient({
chain: celo,
transport: http(),
});
const STABLE_TOKEN_ADDRESS = "0x765DE816845861e75A25fCA122bb6898B8B1282a";
// `estimateGas` implemented above
const gasLimit = await estimateGas(
publicClient,
{
account: "0x8eb02597d85abc268bc4769e06a0d4cc603ab05f",
to: "0x4f93fa058b03953c851efaa2e4fc5c34afdfab84",
value: "0x1",
data: "0x",
},
STABLE_TOKEN_ADDRESS
);
// `estimateGasPrice` implemented above
const gasPrice = await estimateGasPrice(publicClient, STABLE_TOKEN_ADDRESS);
// Convert hex gas price to BigInt and calculate fees
const gasPriceBigInt = fromHex(gasPrice, "bigint");
const transactionFeesInCUSD = formatEther(gasLimit * gasPriceBigInt);
Resolve Minipay phone numbers to Addresses
Install the@celo/identity package:
Copy
Ask AI
npm install @celo/identity
Step 1: Set up your issuer
The issuer is the account registering attestations. When a user requests attestation registration, verify they own the identifier (e.g., SMS verification for phone numbers).Copy
Ask AI
import { createWalletClient, http } from "viem";
import { celoSepolia } from "viem/chains";
import { privateKeyToAccount } from "viem/accounts";
// The issuer is the account that is registering the attestation
const ISSUER_PRIVATE_KEY = "YOUR_ISSUER_PRIVATE_KEY";
// Create Celo Sepolia viem client with the issuer private key
const viemClient = createWalletClient({
account: privateKeyToAccount(ISSUER_PRIVATE_KEY),
transport: http(),
chain: celoSepolia,
});
// Information provided by user, issuer should confirm they own the identifier
const userPlaintextIdentifier = "+12345678910";
const userAccountAddress = "0x000000000000000000000000000000000000user";
// Time at which issuer verified the user owns their identifier
const attestationVerifiedTime = Date.now();
Step 2: Check and top up ODIS quota
Copy
Ask AI
import { OdisUtils } from "@celo/identity";
import { AuthSigner } from "@celo/identity/lib/odis/query";
import { OdisContextName } from "@celo/identity/lib/odis/query";
// authSigner provides information needed to authenticate with ODIS
const authSigner: AuthSigner = {
authenticationMethod: OdisUtils.Query.AuthenticationMethod.WALLET_KEY,
sign191: ({ message, account }) => viemClient.signMessage({ message, account }),
};
// serviceContext provides the ODIS endpoint and public key
const serviceContext = OdisUtils.Query.getServiceContext(
OdisContextName.CELO_SEPOLIA
);
// Check existing quota on issuer account
const issuerAddress = viemClient.account.address;
const { remainingQuota } = await OdisUtils.Quota.getPnpQuotaStatus(
issuerAddress,
authSigner,
serviceContext
);
// If needed, approve and send payment to OdisPayments to get quota for ODIS
// Note: This example uses viem. For contract interactions, use getContract from viem
if (remainingQuota < 1) {
// Use viem's getContract to interact with stable token and ODIS payments contracts
// Implementation depends on your specific contract setup
}
Step 3: Derive the obfuscated identifier
Get the obfuscated identifier from the plaintext identifier by querying ODIS:Copy
Ask AI
const { obfuscatedIdentifier } = await OdisUtils.Identifier.getObfuscatedIdentifier(
userPlaintextIdentifier,
OdisUtils.Identifier.IdentifierPrefix.PHONE_NUMBER,
issuerAddress,
authSigner,
serviceContext
);
Step 4: Look up account addresses
Query the FederatedAttestations contract to look up account addresses owned by an identifier:Copy
Ask AI
const attestations = await federatedAttestationsContract.lookupAttestations(
obfuscatedIdentifier,
[issuerAddress] // Trusted issuers
);
console.log(attestations.accounts);
Request an ERC20 token transfer
Copy
Ask AI
import { createWalletClient, createPublicClient, custom, http, encodeFunctionData, parseUnits } from "viem";
import { celo, celoSepolia } from "viem/chains";
import { stableTokenABI } from "@celo/abis";
const walletClient = createWalletClient({
chain: celoSepolia, // For testnet
// chain: celo, // For mainnet
transport: custom(window.ethereum!),
});
const publicClient = createPublicClient({
chain: celoSepolia, // For testnet
// chain: celo, // For mainnet
transport: http(),
});
async function requestTransfer(tokenAddress, transferValue, tokenDecimals, receiverAddress) {
const hash = await walletClient.sendTransaction({
to: tokenAddress,
// Mainnet addresses:
// cUSD: '0x765DE816845861e75A25fCA122bb6898B8B1282a'
// USDC: '0xcebA9300f2b948710d2653dD7B07f33A8B32118C'
// USDT: '0x48065fbbe25f71c9282ddf5e1cd6d6a887483d5e'
data: encodeFunctionData({
abi: stableTokenABI, // Token ABI from @celo/abis
functionName: "transfer",
args: [
receiverAddress,
// Different tokens can have different decimals, cUSD (18), USDC (6)
parseUnits(`${Number(transferValue)}`, tokenDecimals),
],
}),
});
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.
}
}

