# 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
