// Review Step 9

Step 1: Set Up the Project

First, create a new Eliza plugin project:

# Install Eliza CLI if you haven't already
npm install -g @eliza/cli

# Create a new plugin project
eliza create plugin

During the creation process, you'll be prompted to enter:

Step 2: Install Dependencies

Navigate to your plugin directory and install the required dependencies:

cd payid-eliza-plugin
npm install axios
npm install --save-dev typescript @types/node jest ts-jest @types/jest

Step 3: Define Types

Create a file at src/types/index.ts with the following content:

// Configuration
export interface PayIDPluginConfig {
  apiKey: string;
  baseUrl?: string;
}

// Supported tokens and networks
export type SupportedToken = 'USDT' | 'USDC' | 'ETH' | 'BNB' | 'POL';
export type SupportedNetwork = 'ETH' | 'POL' | 'OP' | 'BNB' | 'BASE';

// Request types
export interface ClaimPayIdRequest {
  name: string;
  userId: string;
}

export interface SearchPayIdsRequest {
  query: string;
  limit?: number;
  activeOnly?: boolean;
}

export interface InitTransactionRequest {
  userId: string;
  amount: number;
  token: SupportedToken;
  network: SupportedNetwork;
  recipientPayId?: string;
  walletAddress?: string;
  message?: string;
}

export interface CreateRouteRequest {
  userId: string;
  name: string;
  incomingNetworks: SupportedNetwork[];
  incomingTokens: SupportedToken[];
  incomingWallets?: string[];
  swapNetwork?: SupportedNetwork;
  swapToken?: SupportedToken;
  outgoingWallet: string;
}

// Response types
export interface PayId {
  id: string;
  name: string;
  userId: string;
  status: 'ACTIVE' | 'INACTIVE';
  createdAt: string;
  updatedAt: string;
}

export interface TransactionInitialize {
  amount: number;
  token: SupportedToken;
  network: SupportedNetwork;
  tx: {
    to: string;
    data: string;
    value: string;
    chainId: number;
  };
  fees?: {
    applicationFee: number | null;
    protocolFee: number | null;
    bridgeFee: number | null;
  };
  approveTx?: {
    to: string;
    data: string;
    chainId: number;
  } | null;
}

export interface TransactionActivity {
  id: string;
  userId: string;
  type: 'SEND' | 'RECEIVE';
  amount: number;
  token: string;
  network: string;
  status: 'PENDING' | 'COMPLETED' | 'FAILED';
  txHash: string;
  createdAt: string;
  updatedAt: string;
}

export interface PayIdRoute {
  id: string;
  name: string;
  incomingWallets: string[];
  incomingNetworks: SupportedNetwork[];
  incomingTokens: SupportedToken[];
  swapNetwork: SupportedNetwork | null;
  swapToken: SupportedToken | null;
  outgoingWallet: string;
  userId: string;
  createdAt: string;
  updatedAt: string;
}

Step 4: Implement the PayID Service

Create a file at src/services/payid.service.ts:

import axios, { AxiosInstance } from 'axios';
import {
  ClaimPayIdRequest,
  SearchPayIdsRequest,
  InitTransactionRequest,
  CreateRouteRequest,
  TransactionActivity,
  PayIdRoute,
  SupportedToken,
  SupportedNetwork
} from '../types';

export class PayIDService {
  private client: AxiosInstance;

  constructor(private apiKey: string, private baseUrl: string) {
    this.client = axios.create({
      baseURL: baseUrl,
      headers: {
        'Authorization': `Bearer ${apiKey}`,
        'Content-Type': 'application/json'
      }
    });
  }

  /**
   * Claim a PayID for a user
   */
  async claimPayId(params: ClaimPayIdRequest): Promise<any> {
    try {
      const response = await this.client.post('/pay-ids', {
        name: params.name,
        userId: params.userId
      });

      const { data } = response.data;
      return data;
    } catch (error) {
      if (error.response?.status === 409) {
        throw new Error('PayID already taken');
      }
      throw new Error(`Failed to claim PayID: ${error.message}`);
    }
  }

  /**
   * Search for PayIDs
   */
  async searchPayIds(params: SearchPayIdsRequest): Promise<any> {
    try {
      const response = await this.client.get('/pay-ids/search', {
        params: {
          q: params.query,
          limit: params.limit || 10,
          activeOnly: params.activeOnly || true
        }
      });

      const { data } = response.data;
      return data.payIds;
    } catch (error) {
      throw new Error(`Failed to search for PayIDs: ${error.message}`);
    }
  }

  /**
   * Initialize a transaction
   */
  async initTransaction(params: InitTransactionRequest): Promise<any> {
    try {
      const payload: any = {
        userId: params.userId,
        amount: params.amount,
        token: params.token,
        network: params.network
      };

      if (params.recipientPayId) {
        payload.recipientPayId = params.recipientPayId;
      } else if (params.walletAddress) {
        payload.walletAddress = params.walletAddress;
      } else {
        throw new Error('Either recipientPayId or walletAddress must be provided');
      }

      if (params.message) {
        payload.message = params.message;
      }

      const response = await this.client.post('/transactions', payload);

      const { data } = response.data;
      return data;
    } catch (error) {
      throw new Error(`Failed to initialize transaction: ${error.message}`);
    }
  }

  /**
   * Create a payment route
   */
  async createRoute(params: CreateRouteRequest): Promise<PayIdRoute> {
    try {
      const response = await this.client.post('/routes', {
        userId: params.userId,
        name: params.name,
        incomingNetworks: params.incomingNetworks,
        incomingTokens: params.incomingTokens,
        incomingWallets: params.incomingWallets || [],
        swapNetwork: params.swapNetwork || null,
        swapToken: params.swapToken || null,
        outgoingWallet: params.outgoingWallet
      });

      const { data } = response.data;
      return data.route;
    } catch (error) {
      throw new Error(`Failed to create route: ${error.message}`);
    }
  }

  /**
   * Get routes for a user
   */
  async getRoutes(userId: string): Promise<PayIdRoute[]> {
    try {
      const response = await this.client.get(`/routes/${userId}`);

      const { data } = response.data;
      return data.routes;
    } catch (error) {
      throw new Error(`Failed to get routes: ${error.message}`);
    }
  }

  /**
   * Delete a route
   */
  async deleteRoute(routeId: string, userId: string): Promise<void> {
    try {
      await this.client.delete(`/routes/${routeId}`, {
        data: {
          userId
        }
      });
    } catch (error) {
      throw new Error(`Failed to delete route: ${error.message}`);
    }
  }

  /**
   * Get transaction history for a user
   */
  async getTransactionHistory(userId: string, page: number = 1, pageSize: number = 10): Promise<{activities: TransactionActivity[], pagination: any}> {
    try {
      const response = await this.client.get(`/transactions/users/${userId}/activities`, {
        params: {
          page,
          pageSize
        }
      });

      const { data } = response.data;
      return data;
    } catch (error) {
      throw new Error(`Failed to get transaction history: ${error.message}`);
    }
  }

  /**
   * Check if a route configuration conflicts with existing routes
   */
  async checkRouteConflicts(userId: string, incomingNetworks: SupportedNetwork[], incomingTokens: SupportedToken[], routeId?: string): Promise<boolean> {
    try {
      const payload: any = {
        userId,
        incomingNetworks,
        incomingTokens
      };

      if (routeId) {
        payload.routeId = routeId;
      }

      const response = await this.client.post('/routes/check-conflicts', payload);

      const { data } = response.data;
      return data.hasConflicts;
    } catch (error) {
      throw new Error(`Failed to check route conflicts: ${error.message}`);
    }
  }

  /**
   * Get supported tokens and networks
   */
  getSupportedTokens(): SupportedToken[] {
    return ['USDT', 'USDC', 'ETH', 'BNB', 'POL'];
  }

  getSupportedNetworks(): SupportedNetwork[] {
    return ['ETH', 'POL', 'OP', 'BNB', 'BASE'];
  }

  /**
   * Validate token/network compatibility
   */
  isValidTokenNetworkPair(token: SupportedToken, network: SupportedNetwork): boolean {
    // Based on the API documentation, these combinations are not supported
    const invalidCombinations = [
      { token: 'POL', network: 'OP' },
      { token: 'BNB', network: 'OP' },
      { token: 'USDT', network: 'BASE' },
      { token: 'BNB', network: 'BASE' },
      { token: 'POL', network: 'BASE' }
    ];

    return !invalidCombinations.some(
      combo => combo.token === token && combo.network === network
    );
  }
}

Step 5: Define Actions

Create a file at src/actions/index.ts: