Skip to content

Commit

Permalink
Merge pull request #658 from monilpat/coinbaseIntegrations
Browse files Browse the repository at this point in the history
feat: Add wallet history (transactions, balances) to coinbase providers
  • Loading branch information
lalalune authored Nov 29, 2024
2 parents b2e2413 + 1b40ad1 commit 62e7417
Show file tree
Hide file tree
Showing 4 changed files with 158 additions and 27 deletions.
23 changes: 22 additions & 1 deletion packages/plugin-coinbase/src/plugins/commerce.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -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 };
},
};

Expand Down
41 changes: 29 additions & 12 deletions packages/plugin-coinbase/src/plugins/massPayments.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand All @@ -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({
Expand All @@ -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: [] };
}
},
};
Expand Down Expand Up @@ -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",
},
},
{
Expand Down
39 changes: 27 additions & 12 deletions packages/plugin-coinbase/src/plugins/trade.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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";
Expand All @@ -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
Expand Down Expand Up @@ -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 [];
Expand Down Expand Up @@ -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",
},
},
{
Expand Down
82 changes: 80 additions & 2 deletions packages/plugin-coinbase/src/utils.ts
Original file line number Diff line number Diff line change
@@ -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 =
Expand Down Expand Up @@ -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<any>}>} - 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.");
}
}

0 comments on commit 62e7417

Please sign in to comment.