SDK Integration with Viem
# SDK Integration with Viem
## Complete Registration Example
```typescript
import {
createWalletClient,
createPublicClient,
http,
parseEther,
type Address,
type Hex
} from "viem";
import { privateKeyToAccount } from "viem/accounts";
import { celoAlfajores } from "viem/chains";
import { OdisUtils } from "@celo/identity";
import { OdisContextName } from "@celo/identity/lib/odis/query";
import type { AuthSigner } from "@celo/identity/lib/odis/query";
import { getContract } from "viem";
import { federatedAttestationsABI, odisPaymentsABI, stableTokenABI } from "@celo/abis";
// Configuration
const ISSUER_PRIVATE_KEY = process.env.ISSUER_PRIVATE_KEY as Hex;
const FEDERATED_ATTESTATIONS_ADDRESS = "0x70F9314aF173c246669cFb0EEe79F9Cfd9C34ee3" as Address;
const ODIS_PAYMENTS_ADDRESS = "0x645170cdB6B5c1bc80847bb728dBa56C50a20a49" as Address;
const STABLE_TOKEN_ADDRESS = "0x874069Fa1Eb16D44d622F2e0Ca25eeA172369bC1" as Address;
// Setup
const account = privateKeyToAccount(ISSUER_PRIVATE_KEY);
const walletClient = createWalletClient({
account,
transport: http(),
chain: celoAlfajores
});
const publicClient = createPublicClient({
transport: http(),
chain: celoAlfajores
});
const issuerAddress = account.address;
// User information (provided by user after verification)
const userPlaintextIdentifier = "+12345678910";
const userAccountAddress = "0x742d35Cc6634C0532925a3b844Bc9e7595f0bEb" as Address;
const attestationVerifiedTime = BigInt(Math.floor(Date.now() / 1000));
async function registerAttestation() {
// 1. Setup authentication
const authSigner: AuthSigner = {
authenticationMethod: OdisUtils.Query.AuthenticationMethod.WALLET_KEY,
sign191: ({ message, account }) =>
walletClient.signMessage({ message, account })
};
const serviceContext = OdisUtils.Query.getServiceContext(
OdisContextName.ALFAJORES
);
// 2. Check and top up ODIS quota if needed
const { remainingQuota } = await OdisUtils.Quota.getPnpQuotaStatus(
issuerAddress,
authSigner,
serviceContext
);
console.log("Remaining quota:", remainingQuota);
if (remainingQuota < 1) {
console.log("Purchasing ODIS quota...");
// Get contract instances
const stableToken = getContract({
address: STABLE_TOKEN_ADDRESS,
abi: stableTokenABI,
client: { public: publicClient, wallet: walletClient }
});
const odisPayments = getContract({
address: ODIS_PAYMENTS_ADDRESS,
abi: odisPaymentsABI,
client: { public: publicClient, wallet: walletClient }
});
const ONE_CENT_CUSD = parseEther("0.01");
// Approve ODIS Payments to spend cUSD
const approveHash = await stableToken.write.approve([
ODIS_PAYMENTS_ADDRESS,
ONE_CENT_CUSD
]);
await publicClient.waitForTransactionReceipt({ hash: approveHash });
// Pay for quota
const paymentHash = await odisPayments.write.payInCUSD([
issuerAddress,
ONE_CENT_CUSD
]);
await publicClient.waitForTransactionReceipt({ hash: paymentHash });
console.log("ODIS quota purchased successfully");
}
// 3. Get obfuscated identifier from ODIS
console.log("Getting obfuscated identifier...");
const { obfuscatedIdentifier } = await OdisUtils.Identifier.getObfuscatedIdentifier(
userPlaintextIdentifier,
OdisUtils.Identifier.IdentifierPrefix.PHONE_NUMBER,
issuerAddress,
authSigner,
serviceContext
);
console.log("Obfuscated Identifier:", obfuscatedIdentifier);
// 4. Register attestation on-chain
console.log("Registering attestation...");
const federatedAttestations = getContract({
address: FEDERATED_ATTESTATIONS_ADDRESS,
abi: federatedAttestationsABI,
client: { public: publicClient, wallet: walletClient }
});
const hash = await federatedAttestations.write.registerAttestationAsIssuer([
obfuscatedIdentifier as Hex,
userAccountAddress,
attestationVerifiedTime
]);
const receipt = await publicClient.waitForTransactionReceipt({ hash });
console.log("Attestation registered!");
console.log("Transaction:", receipt.transactionHash);
return {
obfuscatedIdentifier,
transactionHash: receipt.transactionHash
};
}
// Execute
registerAttestation().catch(console.error);
```
## Complete Lookup Example
```typescript
import { createPublicClient, http, type Address, type Hex } from "viem";
import { celoAlfajores } from "viem/chains";
import { OdisUtils } from "@celo/identity";
import { OdisContextName } from "@celo/identity/lib/odis/query";
import { getContract } from "viem";
import { federatedAttestationsABI } from "@celo/abis";
const FEDERATED_ATTESTATIONS_ADDRESS = "0x70F9314aF173c246669cFb0EEe79F9Cfd9C34ee3" as Address;
const publicClient = createPublicClient({
transport: http(),
chain: celoAlfajores
});
async function lookupIdentifier(
plaintextIdentifier: string,
identifierType: string,
trustedIssuers: Address[]
): Promise<Address[]> {
// 1. Setup authentication for lookup
// For read-only operations, use a zero address
const lookupAddress = "0x0000000000000000000000000000000000000000" as Address;
const authSigner = {
authenticationMethod: OdisUtils.Query.AuthenticationMethod.WALLET_KEY,
sign191: async () => "0x" as Hex
};
const serviceContext = OdisUtils.Query.getServiceContext(
OdisContextName.ALFAJORES
);
// 2. Get obfuscated identifier
const { obfuscatedIdentifier } = await OdisUtils.Identifier.getObfuscatedIdentifier(
plaintextIdentifier,
identifierType,
lookupAddress,
authSigner,
serviceContext
);
console.log("Looking up:", obfuscatedIdentifier);
// 3. Query FederatedAttestations
const federatedAttestations = getContract({
address: FEDERATED_ATTESTATIONS_ADDRESS,
abi: federatedAttestationsABI,
client: publicClient
});
const attestations = await federatedAttestations.read.lookupAttestations([
obfuscatedIdentifier as Hex,
trustedIssuers
]);
const [countsPerIssuer, accounts, signers, issuedOns, publishedOns] = attestations;
// 4. Process results
console.log("Found attestations:");
let accountIndex = 0;
for (let i = 0; i < trustedIssuers.length; i++) {
const count = Number(countsPerIssuer[i]);
console.log(`\nIssuer: ${trustedIssuers[i]}`);
console.log(`Attestation count: ${count}`);
for (let j = 0; j < count; j++) {
console.log(` Account: ${accounts[accountIndex]}`);
console.log(` Signer: ${signers[accountIndex]}`);
console.log(` Issued: ${new Date(Number(issuedOns[accountIndex]) * 1000).toISOString()}`);
console.log(` Published: ${new Date(Number(publishedOns[accountIndex]) * 1000).toISOString()}`);
accountIndex++;
}
}
return accounts as Address[];
}
// Example usage
const trustedIssuers: Address[] = [
"0x6549aF2688e07907C1b821cA44d6d65872737f05", // Kaala
"0x388612590F8cC6577F19c9b61811475Aa432CB44" // Libera
];
lookupIdentifier(
"+12345678910",
OdisUtils.Identifier.IdentifierPrefix.PHONE_NUMBER,
trustedIssuers
).catch(console.error);
```
## Viem Best Practices
**Use Type-Safe Contract Interactions**
```typescript
import { getContract, type Address } from "viem";
import { federatedAttestationsABI } from "@celo/abis";
// Type-safe contract instance
const contract = getContract({
address: FEDERATED_ATTESTATIONS_ADDRESS,
abi: federatedAttestationsABI,
client: { public: publicClient, wallet: walletClient }
});
// TypeScript knows the exact function signatures
const hash = await contract.write.registerAttestationAsIssuer([
obfuscatedIdentifier as `0x${string}`,
userAddress as `0x${string}`,
timestamp
]);
```
**Handle Hex Types Properly**
Viem uses strict `Hex` types for type safety:
```typescript
import type { Hex, Address } from "viem";
// Correct
const privateKey: Hex = process.env.PRIVATE_KEY as Hex;
const address: Address = "0x742d35Cc6634C0532925a3b844Bc9e7595f0bEb" as Address;
// Type assertion for obfuscated identifiers
const obfuscatedIdentifier: Hex = result.obfuscatedIdentifier as Hex;
```
**Use Proper Error Handling**
```typescript
import {
ContractFunctionExecutionError,
TransactionExecutionError
} from "viem";
try {
const hash = await contract.write.registerAttestationAsIssuer([...]);
} catch (error) {
if (error instanceof ContractFunctionExecutionError) {
console.error("Contract error:", error.shortMessage);
} else if (error instanceof TransactionExecutionError) {
console.error("Transaction failed:", error.message);
} else {
console.error("Unknown error:", error);
}
}
```
**Optimize with Public Actions**
For read-only operations, use public client directly:
```typescript
import { createPublicClient, http } from "viem";
import { celo } from "viem/chains";
const publicClient = createPublicClient({
chain: celo,
transport: http()
});
// No wallet needed for reads
const attestations = await publicClient.readContract({
address: FEDERATED_ATTESTATIONS_ADDRESS,
abi: federatedAttestationsABI,
functionName: "lookupAttestations",
args: [obfuscatedIdentifier, trustedIssuers]
});
```
## Web/Browser
Browser environments require careful handling of wallet connections:
```typescript
// Check for wallet
if (window.ethereum) {
const accounts = await window.ethereum.request({
method: "eth_requestAccounts"
});
// Create client with injected provider
const walletClient = createWalletClient({
account: accounts[0],
transport: custom(window.ethereum),
chain: celoAlfajores
});
}
```
**Next.js Example:**
```typescript
// pages/api/register-attestation.ts
import type { NextApiRequest, NextApiResponse } from "next";
import { OdisUtils } from "@celo/identity";
export default async function handler(
req: NextApiRequest,
res: NextApiResponse
) {
const { phoneNumber, userAddress } = req.body;
try {
// Verify phone number (your verification logic)
const isVerified = await verifyPhoneNumber(phoneNumber);
if (!isVerified) {
return res.status(400).json({ error: "Verification failed" });
}
// Register attestation
const result = await registerAttestation(phoneNumber, userAddress);
res.status(200).json(result);
} catch (error) {
res.status(500).json({ error: error.message });
}
}
```
## Custom Identifier Types
Create custom identifier types for your use case:
```typescript
// Define custom prefix
const CUSTOM_PREFIX = "custom-app";
async function registerCustomIdentifier(
customId: string,
userAddress: string
) {
const { obfuscatedIdentifier } = await OdisUtils.Identifier.getObfuscatedIdentifier(
customId,
CUSTOM_PREFIX, // Custom prefix
issuerAddress,
authSigner,
serviceContext
);
// Register as usual
await registerAttestation(obfuscatedIdentifier, userAddress);
}
```
**Best Practices for Custom Identifiers:**
* Use descriptive prefixes (e.g., `myapp://` not `ma://`)
* Document your prefix for ecosystem adoption
* Consider standardization if widely applicable
* Ensure identifiers are unique and verifiable
On this page
Complete Registration Example
import {
createWalletClient,
createPublicClient,
http,
parseEther,
type Address,
type Hex
} from "viem";
import { privateKeyToAccount } from "viem/accounts";
import { celoAlfajores } from "viem/chains";
import { OdisUtils } from "@celo/identity";
import { OdisContextName } from "@celo/identity/lib/odis/query";
import type { AuthSigner } from "@celo/identity/lib/odis/query";
import { getContract } from "viem";
import { federatedAttestationsABI, odisPaymentsABI, stableTokenABI } from "@celo/abis";
// Configuration
const ISSUER_PRIVATE_KEY = process.env.ISSUER_PRIVATE_KEY as Hex;
const FEDERATED_ATTESTATIONS_ADDRESS = "0x70F9314aF173c246669cFb0EEe79F9Cfd9C34ee3" as Address;
const ODIS_PAYMENTS_ADDRESS = "0x645170cdB6B5c1bc80847bb728dBa56C50a20a49" as Address;
const STABLE_TOKEN_ADDRESS = "0x874069Fa1Eb16D44d622F2e0Ca25eeA172369bC1" as Address;
// Setup
const account = privateKeyToAccount(ISSUER_PRIVATE_KEY);
const walletClient = createWalletClient({
account,
transport: http(),
chain: celoAlfajores
});
const publicClient = createPublicClient({
transport: http(),
chain: celoAlfajores
});
const issuerAddress = account.address;
// User information (provided by user after verification)
const userPlaintextIdentifier = "+12345678910";
const userAccountAddress = "0x742d35Cc6634C0532925a3b844Bc9e7595f0bEb" as Address;
const attestationVerifiedTime = BigInt(Math.floor(Date.now() / 1000));
async function registerAttestation() {
// 1. Setup authentication
const authSigner: AuthSigner = {
authenticationMethod: OdisUtils.Query.AuthenticationMethod.WALLET_KEY,
sign191: ({ message, account }) =>
walletClient.signMessage({ message, account })
};
const serviceContext = OdisUtils.Query.getServiceContext(
OdisContextName.ALFAJORES
);
// 2. Check and top up ODIS quota if needed
const { remainingQuota } = await OdisUtils.Quota.getPnpQuotaStatus(
issuerAddress,
authSigner,
serviceContext
);
console.log("Remaining quota:", remainingQuota);
if (remainingQuota < 1) {
console.log("Purchasing ODIS quota...");
// Get contract instances
const stableToken = getContract({
address: STABLE_TOKEN_ADDRESS,
abi: stableTokenABI,
client: { public: publicClient, wallet: walletClient }
});
const odisPayments = getContract({
address: ODIS_PAYMENTS_ADDRESS,
abi: odisPaymentsABI,
client: { public: publicClient, wallet: walletClient }
});
const ONE_CENT_CUSD = parseEther("0.01");
// Approve ODIS Payments to spend cUSD
const approveHash = await stableToken.write.approve([
ODIS_PAYMENTS_ADDRESS,
ONE_CENT_CUSD
]);
await publicClient.waitForTransactionReceipt({ hash: approveHash });
// Pay for quota
const paymentHash = await odisPayments.write.payInCUSD([
issuerAddress,
ONE_CENT_CUSD
]);
await publicClient.waitForTransactionReceipt({ hash: paymentHash });
console.log("ODIS quota purchased successfully");
}
// 3. Get obfuscated identifier from ODIS
console.log("Getting obfuscated identifier...");
const { obfuscatedIdentifier } = await OdisUtils.Identifier.getObfuscatedIdentifier(
userPlaintextIdentifier,
OdisUtils.Identifier.IdentifierPrefix.PHONE_NUMBER,
issuerAddress,
authSigner,
serviceContext
);
console.log("Obfuscated Identifier:", obfuscatedIdentifier);
// 4. Register attestation on-chain
console.log("Registering attestation...");
const federatedAttestations = getContract({
address: FEDERATED_ATTESTATIONS_ADDRESS,
abi: federatedAttestationsABI,
client: { public: publicClient, wallet: walletClient }
});
const hash = await federatedAttestations.write.registerAttestationAsIssuer([
obfuscatedIdentifier as Hex,
userAccountAddress,
attestationVerifiedTime
]);
const receipt = await publicClient.waitForTransactionReceipt({ hash });
console.log("Attestation registered!");
console.log("Transaction:", receipt.transactionHash);
return {
obfuscatedIdentifier,
transactionHash: receipt.transactionHash
};
}
// Execute
registerAttestation().catch(console.error);
Complete Lookup Example
import { createPublicClient, http, type Address, type Hex } from "viem";
import { celoAlfajores } from "viem/chains";
import { OdisUtils } from "@celo/identity";
import { OdisContextName } from "@celo/identity/lib/odis/query";
import { getContract } from "viem";
import { federatedAttestationsABI } from "@celo/abis";
const FEDERATED_ATTESTATIONS_ADDRESS = "0x70F9314aF173c246669cFb0EEe79F9Cfd9C34ee3" as Address;
const publicClient = createPublicClient({
transport: http(),
chain: celoAlfajores
});
async function lookupIdentifier(
plaintextIdentifier: string,
identifierType: string,
trustedIssuers: Address[]
): Promise<Address[]> {
// 1. Setup authentication for lookup
// For read-only operations, use a zero address
const lookupAddress = "0x0000000000000000000000000000000000000000" as Address;
const authSigner = {
authenticationMethod: OdisUtils.Query.AuthenticationMethod.WALLET_KEY,
sign191: async () => "0x" as Hex
};
const serviceContext = OdisUtils.Query.getServiceContext(
OdisContextName.ALFAJORES
);
// 2. Get obfuscated identifier
const { obfuscatedIdentifier } = await OdisUtils.Identifier.getObfuscatedIdentifier(
plaintextIdentifier,
identifierType,
lookupAddress,
authSigner,
serviceContext
);
console.log("Looking up:", obfuscatedIdentifier);
// 3. Query FederatedAttestations
const federatedAttestations = getContract({
address: FEDERATED_ATTESTATIONS_ADDRESS,
abi: federatedAttestationsABI,
client: publicClient
});
const attestations = await federatedAttestations.read.lookupAttestations([
obfuscatedIdentifier as Hex,
trustedIssuers
]);
const [countsPerIssuer, accounts, signers, issuedOns, publishedOns] = attestations;
// 4. Process results
console.log("Found attestations:");
let accountIndex = 0;
for (let i = 0; i < trustedIssuers.length; i++) {
const count = Number(countsPerIssuer[i]);
console.log(`\nIssuer: ${trustedIssuers[i]}`);
console.log(`Attestation count: ${count}`);
for (let j = 0; j < count; j++) {
console.log(` Account: ${accounts[accountIndex]}`);
console.log(` Signer: ${signers[accountIndex]}`);
console.log(` Issued: ${new Date(Number(issuedOns[accountIndex]) * 1000).toISOString()}`);
console.log(` Published: ${new Date(Number(publishedOns[accountIndex]) * 1000).toISOString()}`);
accountIndex++;
}
}
return accounts as Address[];
}
// Example usage
const trustedIssuers: Address[] = [
"0x6549aF2688e07907C1b821cA44d6d65872737f05", // Kaala
"0x388612590F8cC6577F19c9b61811475Aa432CB44" // Libera
];
lookupIdentifier(
"+12345678910",
OdisUtils.Identifier.IdentifierPrefix.PHONE_NUMBER,
trustedIssuers
).catch(console.error);
Viem Best Practices
Use Type-Safe Contract Interactions
import { getContract, type Address } from "viem";
import { federatedAttestationsABI } from "@celo/abis";
// Type-safe contract instance
const contract = getContract({
address: FEDERATED_ATTESTATIONS_ADDRESS,
abi: federatedAttestationsABI,
client: { public: publicClient, wallet: walletClient }
});
// TypeScript knows the exact function signatures
const hash = await contract.write.registerAttestationAsIssuer([
obfuscatedIdentifier as `0x${string}`,
userAddress as `0x${string}`,
timestamp
]);
Handle Hex Types Properly
Viem uses strict Hex types for type safety:
import type { Hex, Address } from "viem";
// Correct
const privateKey: Hex = process.env.PRIVATE_KEY as Hex;
const address: Address = "0x742d35Cc6634C0532925a3b844Bc9e7595f0bEb" as Address;
// Type assertion for obfuscated identifiers
const obfuscatedIdentifier: Hex = result.obfuscatedIdentifier as Hex;
Use Proper Error Handling
import {
ContractFunctionExecutionError,
TransactionExecutionError
} from "viem";
try {
const hash = await contract.write.registerAttestationAsIssuer([...]);
} catch (error) {
if (error instanceof ContractFunctionExecutionError) {
console.error("Contract error:", error.shortMessage);
} else if (error instanceof TransactionExecutionError) {
console.error("Transaction failed:", error.message);
} else {
console.error("Unknown error:", error);
}
}
Optimize with Public Actions
For read-only operations, use public client directly:
import { createPublicClient, http } from "viem";
import { celo } from "viem/chains";
const publicClient = createPublicClient({
chain: celo,
transport: http()
});
// No wallet needed for reads
const attestations = await publicClient.readContract({
address: FEDERATED_ATTESTATIONS_ADDRESS,
abi: federatedAttestationsABI,
functionName: "lookupAttestations",
args: [obfuscatedIdentifier, trustedIssuers]
});
Web/Browser
Browser environments require careful handling of wallet connections:
// Check for wallet
if (window.ethereum) {
const accounts = await window.ethereum.request({
method: "eth_requestAccounts"
});
// Create client with injected provider
const walletClient = createWalletClient({
account: accounts[0],
transport: custom(window.ethereum),
chain: celoAlfajores
});
}
Next.js Example:
// pages/api/register-attestation.ts
import type { NextApiRequest, NextApiResponse } from "next";
import { OdisUtils } from "@celo/identity";
export default async function handler(
req: NextApiRequest,
res: NextApiResponse
) {
const { phoneNumber, userAddress } = req.body;
try {
// Verify phone number (your verification logic)
const isVerified = await verifyPhoneNumber(phoneNumber);
if (!isVerified) {
return res.status(400).json({ error: "Verification failed" });
}
// Register attestation
const result = await registerAttestation(phoneNumber, userAddress);
res.status(200).json(result);
} catch (error) {
res.status(500).json({ error: error.message });
}
}
Custom Identifier Types
Create custom identifier types for your use case:
// Define custom prefix
const CUSTOM_PREFIX = "custom-app";
async function registerCustomIdentifier(
customId: string,
userAddress: string
) {
const { obfuscatedIdentifier } = await OdisUtils.Identifier.getObfuscatedIdentifier(
customId,
CUSTOM_PREFIX, // Custom prefix
issuerAddress,
authSigner,
serviceContext
);
// Register as usual
await registerAttestation(obfuscatedIdentifier, userAddress);
}
Best Practices for Custom Identifiers:
- Use descriptive prefixes (e.g.,
myapp://notma://) - Document your prefix for ecosystem adoption
- Consider standardization if widely applicable
- Ensure identifiers are unique and verifiable
Was this page helpful?
Thanks for your feedback!