diff --git a/packages/plugin-coinbase/src/plugins/commerce.ts b/packages/plugin-coinbase/src/plugins/commerce.ts index 44ba4e4346..89b9e82c09 100644 --- a/packages/plugin-coinbase/src/plugins/commerce.ts +++ b/packages/plugin-coinbase/src/plugins/commerce.ts @@ -15,6 +15,8 @@ import { } from "@ai16z/eliza"; import { ChargeContent, ChargeSchema, isChargeContent } from "../types"; import { chargeTemplate, getChargeTemplate } from "../templates"; +import { getWalletDetails } from "../utils"; +import { Coinbase } from "@coinbase/coinbase-sdk"; const url = "https://api.commerce.coinbase.com/charges"; interface ChargeRequest { @@ -420,7 +422,26 @@ export const chargeProvider: Provider = { const charges = await getAllCharges( runtime.getSetting("COINBASE_COMMERCE_KEY") ); - return charges.data; + // Ensure API key is available + const coinbaseAPIKey = + runtime.getSetting("COINBASE_API_KEY") ?? + process.env.COINBASE_API_KEY; + const coinbasePrivateKey = + runtime.getSetting("COINBASE_PRIVATE_KEY") ?? + process.env.COINBASE_PRIVATE_KEY; + let balances = []; + let transactions = []; + if (coinbaseAPIKey && coinbasePrivateKey) { + Coinbase.configure({ + apiKeyName: coinbaseAPIKey, + privateKey: coinbasePrivateKey, + }); + const { balances, transactions } = await getWalletDetails(runtime); + elizaLogger.log("Current Balances:", balances); + elizaLogger.log("Last Transactions:", transactions); + } + elizaLogger.log("Charges:", charges); + return { charges: charges.data, balances, transactions }; }, }; diff --git a/packages/plugin-coinbase/src/plugins/massPayments.ts b/packages/plugin-coinbase/src/plugins/massPayments.ts index 1b0de06701..8d14324a8f 100644 --- a/packages/plugin-coinbase/src/plugins/massPayments.ts +++ b/packages/plugin-coinbase/src/plugins/massPayments.ts @@ -25,7 +25,7 @@ import path from "path"; import { fileURLToPath } from "url"; import fs from "fs"; import { createArrayCsvWriter } from "csv-writer"; -import { initializeWallet } from "../utils"; +import { getWalletDetails, initializeWallet } from "../utils"; // Dynamically resolve the file path to the src/plugins directory const __filename = fileURLToPath(import.meta.url); @@ -34,11 +34,19 @@ const baseDir = path.resolve(__dirname, "../../plugin-coinbase/src/plugins"); const csvFilePath = path.join(baseDir, "transactions.csv"); export const massPayoutProvider: Provider = { - get: async (_runtime: IAgentRuntime, _message: Memory) => { + get: async (runtime: IAgentRuntime, _message: Memory) => { try { + Coinbase.configure({ + apiKeyName: + runtime.getSetting("COINBASE_API_KEY") ?? + process.env.COINBASE_API_KEY, + privateKey: + runtime.getSetting("COINBASE_PRIVATE_KEY") ?? + process.env.COINBASE_PRIVATE_KEY, + }); elizaLogger.log("Reading CSV file from:", csvFilePath); - // Check if the file exists; if not, create it with headers + // Ensure the CSV file exists if (!fs.existsSync(csvFilePath)) { elizaLogger.warn("CSV file not found. Creating a new one."); const csvWriter = createArrayCsvWriter({ @@ -62,17 +70,26 @@ export const massPayoutProvider: Provider = { skip_empty_lines: true, }); + const { balances, transactions } = await getWalletDetails(runtime); + elizaLogger.log("Parsed CSV records:", records); - return records.map((record: any) => ({ - address: record["Address"] || undefined, - amount: parseFloat(record["Amount"]) || undefined, - status: record["Status"] || undefined, - errorCode: record["Error Code"] || "", - transactionUrl: record["Transaction URL"] || "", - })); + elizaLogger.log("Current Balances:", balances); + elizaLogger.log("Last Transactions:", transactions); + + return { + currentTransactions: records.map((record: any) => ({ + address: record["Address"] || undefined, + amount: parseFloat(record["Amount"]) || undefined, + status: record["Status"] || undefined, + errorCode: record["Error Code"] || "", + transactionUrl: record["Transaction URL"] || "", + })), + balances, + transactionHistory: transactions, + }; } catch (error) { elizaLogger.error("Error in massPayoutProvider:", error); - return []; + return { csvRecords: [], balances: [], transactions: [] }; } }, }; @@ -367,7 +384,7 @@ Check the CSV file for full details.`, { user: "{{user1}}", content: { - text: "Distribute 0.0001 ETH on base network to 0xA0ba2ACB5846A54834173fB0DD9444F756810f06 and 0xF14F2c49aa90BaFA223EE074C1C33b59891826bF", + text: "Distribute 0.0001 ETH on base to 0xA0ba2ACB5846A54834173fB0DD9444F756810f06 and 0xF14F2c49aa90BaFA223EE074C1C33b59891826bF", }, }, { diff --git a/packages/plugin-coinbase/src/plugins/trade.ts b/packages/plugin-coinbase/src/plugins/trade.ts index a8b06d03d5..9263280827 100644 --- a/packages/plugin-coinbase/src/plugins/trade.ts +++ b/packages/plugin-coinbase/src/plugins/trade.ts @@ -12,7 +12,7 @@ import { ModelClass, Provider, } from "@ai16z/eliza"; -import { initializeWallet } from "../utils"; +import { getWalletDetails, initializeWallet } from "../utils"; import { tradeTemplate } from "../templates"; import { isTradeContent, TradeContent, TradeSchema } from "../types"; import { readFile } from "fs/promises"; @@ -29,8 +29,16 @@ const baseDir = path.resolve(__dirname, "../../plugin-coinbase/src/plugins"); const tradeCsvFilePath = path.join(baseDir, "trades.csv"); export const tradeProvider: Provider = { - get: async (_runtime: IAgentRuntime, _message: Memory) => { + get: async (runtime: IAgentRuntime, _message: Memory) => { try { + Coinbase.configure({ + apiKeyName: + runtime.getSetting("COINBASE_API_KEY") ?? + process.env.COINBASE_API_KEY, + privateKey: + runtime.getSetting("COINBASE_PRIVATE_KEY") ?? + process.env.COINBASE_PRIVATE_KEY, + }); elizaLogger.log("Reading CSV file from:", tradeCsvFilePath); // Check if the file exists; if not, create it with headers @@ -60,15 +68,22 @@ export const tradeProvider: Provider = { }); elizaLogger.log("Parsed CSV records:", records); - return records.map((record: any) => ({ - network: record["Network"] || undefined, - amount: parseFloat(record["From Amount"]) || undefined, - sourceAsset: record["Source Asset"] || undefined, - toAmount: parseFloat(record["To Amount"]) || undefined, - targetAsset: record["Target Asset"] || undefined, - status: record["Status"] || undefined, - transactionUrl: record["Transaction URL"] || "", - })); + const { balances, transactions } = await getWalletDetails(runtime); + elizaLogger.log("Current Balances:", balances); + elizaLogger.log("Last Transactions:", transactions); + return { + currentTrades: records.map((record: any) => ({ + network: record["Network"] || undefined, + amount: parseFloat(record["From Amount"]) || undefined, + sourceAsset: record["Source Asset"] || undefined, + toAmount: parseFloat(record["To Amount"]) || undefined, + targetAsset: record["Target Asset"] || undefined, + status: record["Status"] || undefined, + transactionUrl: record["Transaction URL"] || "", + })), + balances, + transactions, + }; } catch (error) { elizaLogger.error("Error in tradeProvider:", error); return []; @@ -232,7 +247,7 @@ export const executeTradeAction: Action = { { user: "{{user1}}", content: { - text: "Trade 0.00001 ETH for USDC on the base", + text: "Trade 0.00001 ETH for USDC on base", }, }, { diff --git a/packages/plugin-coinbase/src/utils.ts b/packages/plugin-coinbase/src/utils.ts index c00ebf2c95..a727d4941c 100644 --- a/packages/plugin-coinbase/src/utils.ts +++ b/packages/plugin-coinbase/src/utils.ts @@ -1,11 +1,12 @@ -import { Wallet, WalletData } from "@coinbase/coinbase-sdk"; +import { Coinbase, Wallet, WalletData } from "@coinbase/coinbase-sdk"; import { elizaLogger, IAgentRuntime } from "@ai16z/eliza"; import fs from "fs"; import path from "path"; +import { EthereumTransaction } from "@coinbase/coinbase-sdk/dist/client"; export async function initializeWallet( runtime: IAgentRuntime, - networkId: string + networkId: string = Coinbase.networks.EthereumMainnet ) { let wallet: Wallet; const storedSeed = @@ -123,3 +124,80 @@ export async function updateCharacterSecrets( } return true; } + +export const getAssetType = (transaction: EthereumTransaction) => { + // Check for ETH + if (transaction.value && transaction.value !== "0") { + return "ETH"; + } + + // Check for ERC-20 tokens + if (transaction.token_transfers && transaction.token_transfers.length > 0) { + return transaction.token_transfers + .map((transfer) => { + return transfer.token_id; + }) + .join(", "); + } + + return "N/A"; +}; + +/** + * Fetches and formats wallet balances and recent transactions. + * + * @param {IAgentRuntime} runtime - The runtime for wallet initialization. + * @param {string} networkId - The network ID (optional, defaults to ETH mainnet). + * @returns {Promise<{balances: Array<{asset: string, amount: string}>, transactions: Array}>} - An object with formatted balances and transactions. + */ +export async function getWalletDetails( + runtime: IAgentRuntime, + networkId: string = Coinbase.networks.EthereumMainnet +): Promise<{ + balances: Array<{ asset: string; amount: string }>; + transactions: Array<{ + timestamp: string; + amount: string; + asset: string; // Ensure getAssetType is implemented + status: string; + transactionUrl: string; + }>; +}> { + try { + // Initialize the wallet, defaulting to the specified network or ETH mainnet + const wallet = await initializeWallet(runtime, networkId); + + // Fetch balances + const balances = await wallet.listBalances(); + const formattedBalances = Array.from(balances, (balance) => ({ + asset: balance[0], + amount: balance[1].toString(), + })); + + // Fetch the wallet's recent transactions + const walletAddress = await wallet.getDefaultAddress(); + const transactions = ( + await walletAddress.listTransactions({ limit: 10 }) + ).data; + + const formattedTransactions = transactions.map((transaction) => { + const content = transaction.content(); + return { + timestamp: content.block_timestamp || "N/A", + amount: content.value || "N/A", + asset: getAssetType(content) || "N/A", // Ensure getAssetType is implemented + status: transaction.getStatus(), + transactionUrl: transaction.getTransactionLink() || "N/A", + }; + }); + + // Return formatted data + return { + balances: formattedBalances, + transactions: formattedTransactions, + }; + } catch (error) { + console.error("Error fetching wallet details:", error); + throw new Error("Unable to retrieve wallet details."); + } +}