From 1763478d2c1ff088e094e934069d1b88807ac84b Mon Sep 17 00:00:00 2001 From: Monil Patel Date: Thu, 28 Nov 2024 13:59:25 -0800 Subject: [PATCH 1/3] feat: Add wallet history (transactions, balances) to provider --- .../plugin-coinbase/src/plugins/commerce.ts | 7 +- .../src/plugins/massPayments.ts | 31 ++++--- packages/plugin-coinbase/src/plugins/trade.ts | 29 ++++--- packages/plugin-coinbase/src/utils.ts | 80 ++++++++++++++++++- 4 files changed, 122 insertions(+), 25 deletions(-) diff --git a/packages/plugin-coinbase/src/plugins/commerce.ts b/packages/plugin-coinbase/src/plugins/commerce.ts index 44ba4e4346..82c7fad75b 100644 --- a/packages/plugin-coinbase/src/plugins/commerce.ts +++ b/packages/plugin-coinbase/src/plugins/commerce.ts @@ -15,6 +15,7 @@ import { } from "@ai16z/eliza"; import { ChargeContent, ChargeSchema, isChargeContent } from "../types"; import { chargeTemplate, getChargeTemplate } from "../templates"; +import { getWalletDetails } from "../utils"; const url = "https://api.commerce.coinbase.com/charges"; interface ChargeRequest { @@ -420,7 +421,11 @@ export const chargeProvider: Provider = { const charges = await getAllCharges( runtime.getSetting("COINBASE_COMMERCE_KEY") ); - return charges.data; + const { balances, transactions } = await getWalletDetails(runtime); + elizaLogger.log("Charges:", charges); + elizaLogger.log("Current Balances:", balances); + elizaLogger.log("Last Transactions:", transactions); + 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..d5911dc33f 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,11 @@ 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 { 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 +62,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: [] }; } }, }; diff --git a/packages/plugin-coinbase/src/plugins/trade.ts b/packages/plugin-coinbase/src/plugins/trade.ts index a8b06d03d5..3dc4c6ba5b 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,7 +29,7 @@ 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 { elizaLogger.log("Reading CSV file from:", tradeCsvFilePath); @@ -60,15 +60,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 []; diff --git a/packages/plugin-coinbase/src/utils.ts b/packages/plugin-coinbase/src/utils.ts index c00ebf2c95..b300dc8584 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,78 @@ 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()).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."); + } +} From 39d5f44f2b7bf1812a22231043815df1b0085585 Mon Sep 17 00:00:00 2001 From: Monil Patel Date: Thu, 28 Nov 2024 14:21:49 -0800 Subject: [PATCH 2/3] test and get working --- .../plugin-coinbase/src/plugins/commerce.ts | 22 ++++++++++++++++--- .../src/plugins/massPayments.ts | 10 ++++++++- packages/plugin-coinbase/src/plugins/trade.ts | 10 ++++++++- packages/plugin-coinbase/src/utils.ts | 4 +++- 4 files changed, 40 insertions(+), 6 deletions(-) diff --git a/packages/plugin-coinbase/src/plugins/commerce.ts b/packages/plugin-coinbase/src/plugins/commerce.ts index 82c7fad75b..89b9e82c09 100644 --- a/packages/plugin-coinbase/src/plugins/commerce.ts +++ b/packages/plugin-coinbase/src/plugins/commerce.ts @@ -16,6 +16,7 @@ import { 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 { @@ -421,10 +422,25 @@ export const chargeProvider: Provider = { const charges = await getAllCharges( runtime.getSetting("COINBASE_COMMERCE_KEY") ); - const { balances, transactions } = await getWalletDetails(runtime); + // 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); - elizaLogger.log("Current Balances:", balances); - elizaLogger.log("Last Transactions:", transactions); 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 d5911dc33f..ee9c219564 100644 --- a/packages/plugin-coinbase/src/plugins/massPayments.ts +++ b/packages/plugin-coinbase/src/plugins/massPayments.ts @@ -36,6 +36,14 @@ const csvFilePath = path.join(baseDir, "transactions.csv"); export const massPayoutProvider: Provider = { 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); // Ensure the CSV file exists @@ -376,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 3dc4c6ba5b..9263280827 100644 --- a/packages/plugin-coinbase/src/plugins/trade.ts +++ b/packages/plugin-coinbase/src/plugins/trade.ts @@ -31,6 +31,14 @@ const tradeCsvFilePath = path.join(baseDir, "trades.csv"); export const tradeProvider: Provider = { 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 @@ -239,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 b300dc8584..a727d4941c 100644 --- a/packages/plugin-coinbase/src/utils.ts +++ b/packages/plugin-coinbase/src/utils.ts @@ -176,7 +176,9 @@ export async function getWalletDetails( // Fetch the wallet's recent transactions const walletAddress = await wallet.getDefaultAddress(); - const transactions = (await walletAddress.listTransactions()).data; + const transactions = ( + await walletAddress.listTransactions({ limit: 10 }) + ).data; const formattedTransactions = transactions.map((transaction) => { const content = transaction.content(); From 1b40ad1d44b67a67722b9ac5af70f2a9db23d7ca Mon Sep 17 00:00:00 2001 From: Monil Patel Date: Thu, 28 Nov 2024 14:24:42 -0800 Subject: [PATCH 3/3] Fix linter --- packages/plugin-coinbase/src/plugins/massPayments.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/plugin-coinbase/src/plugins/massPayments.ts b/packages/plugin-coinbase/src/plugins/massPayments.ts index ee9c219564..8d14324a8f 100644 --- a/packages/plugin-coinbase/src/plugins/massPayments.ts +++ b/packages/plugin-coinbase/src/plugins/massPayments.ts @@ -34,7 +34,7 @@ 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: