From f814e539a1d80319ff00ae1a3d5dee4d00d11440 Mon Sep 17 00:00:00 2001 From: Owen Wu Date: Fri, 24 Jan 2025 07:47:34 +0800 Subject: [PATCH] feat: impl assets provider --- .../plugin-rooch/src/plugins/roochPlugin.ts | 5 +- .../src/providers/assetsProvider.ts | 173 ++++++++++++++++++ .../src/providers/sampleProvider.ts | 14 -- packages/plugin-rooch/src/types.ts | 12 ++ packages/plugin-rooch/src/utils.ts | 10 +- 5 files changed, 197 insertions(+), 17 deletions(-) create mode 100644 packages/plugin-rooch/src/providers/assetsProvider.ts delete mode 100644 packages/plugin-rooch/src/providers/sampleProvider.ts diff --git a/packages/plugin-rooch/src/plugins/roochPlugin.ts b/packages/plugin-rooch/src/plugins/roochPlugin.ts index c9cb21c32d..1fc11f6233 100644 --- a/packages/plugin-rooch/src/plugins/roochPlugin.ts +++ b/packages/plugin-rooch/src/plugins/roochPlugin.ts @@ -1,11 +1,14 @@ import { Plugin } from "@elizaos/core"; +import { assetsProvider } from "../providers/assetsProvider" export const roochPlugin: Plugin = { name: "rooch", description: "Rooch Plugin for Eliza", actions: [], evaluators: [], - providers: [], + providers: [ + assetsProvider + ], }; export default roochPlugin; \ No newline at end of file diff --git a/packages/plugin-rooch/src/providers/assetsProvider.ts b/packages/plugin-rooch/src/providers/assetsProvider.ts new file mode 100644 index 0000000000..bc6190260a --- /dev/null +++ b/packages/plugin-rooch/src/providers/assetsProvider.ts @@ -0,0 +1,173 @@ +import BigNumber from "bignumber.js"; +import { + Provider, + IAgentRuntime, + Memory, + State, + elizaLogger, + settings +} from "@elizaos/core"; +import { + getRoochNodeUrl, + NetworkType, + RoochClient, + Address, + PaginatedUTXOStateViews, + IndexerStateIDView, + PaginatedBalanceInfoViews, +} from '@roochnetwork/rooch-sdk/dist/esm'; +import { + BTCUTXO, + RoochCoin +} from "../types"; +import { + parseBitcoinAddress, +} from "../utils"; + +export class Assets { + utxos: Array + coins: Array +} + +export class AssetsProvider { + private roochClient: RoochClient + private address: Address + + constructor(roochClient: RoochClient, address: Address) { + this.roochClient = roochClient; + this.address = address; + } + + async fetchBTCUTXOs(): Promise> { + try { + const limit = 100 + let cursor: IndexerStateIDView | null = null + const allUTXOs = new Array(); + + while (true) { + const response: PaginatedUTXOStateViews = await this.roochClient.queryUTXO({ + filter: { + owner: this.address.toStr(), + }, + cursor: cursor, + limit: limit.toString(), + }) + + for (const utxo of response.data) { + allUTXOs.push({ + utxo: utxo.id, + sats: utxo.value.value + }) + } + + if (!response.has_next_page || !response.next_cursor) { + break + } + + cursor = response.next_cursor + } + + return allUTXOs; + } catch (error) { + console.error("Error fetching UTXOs:", error); + throw error; + } + } + + async fetchRoochCoins(): Promise> { + try { + const limit = 100 + let cursor: IndexerStateIDView | null = null + const allCoins = new Array(); + + while (true) { + const response: PaginatedBalanceInfoViews = await this.roochClient.getBalances({ + owner: this.address.toStr(), + cursor: cursor, + limit: limit.toString(), + }) + + for (const coin of response.data) { + allCoins.push({ + symbol: coin.symbol, + name: coin.name, + balance: coin.fixedBalance + }) + } + + if (!response.has_next_page || !response.next_cursor) { + break + } + + cursor = response.next_cursor + } + + return allCoins; + } catch (error) { + console.error("Error fetching UTXOs:", error); + throw error; + } + } + + async fetchAssets(): Promise { + try { + const assets = new Assets(); + assets.utxos = await this.fetchBTCUTXOs(); + assets.coins = await this.fetchRoochCoins(); + return assets + } catch (error) { + console.error("Error fetching portfolio:", error); + throw error; + } + } + + formatAssets(runtime, assets: Assets): string { + let output = `${runtime.character.name}\n`; + output += `Assets(${this.address}): \n`; + + output += "Rooch network coin assets:\n" + for (const coin of assets.coins) { + const coinBalanceFormatted = new BigNumber(coin.balance).toFixed(2); + output += `${coin.symbol}(${coin.name}) Balance:${coinBalanceFormatted}\n` + } + + output += "BTC assets:" + for (const utxo of assets.utxos) { + const satsFormatted = new BigNumber(utxo.sats).toFixed(2); + output += `UTXO(${utxo.utxo}) ${satsFormatted} Sats\n` + } + + return output; + } + + async getFormattedAssets(runtime): Promise { + try { + const assets = await this.fetchAssets(); + return this.formatAssets(runtime, assets); + } catch (error) { + console.error("Error generating assets report:", error); + return "Unable to fetch wallet information. Please try again later."; + } + } +} + +export const assetsProvider: Provider = { + get: async (runtime: IAgentRuntime, message: Memory, state: State) => { + const roochAddress = parseBitcoinAddress(runtime); + + try { + const url = getRoochNodeUrl(settings["ROOCH_NETWORK"] as NetworkType); + elizaLogger.info( + `getRoochNodeUrl: ${url}` + ); + + const roochClient = new RoochClient({ url: url }) + const provider = new AssetsProvider(roochClient, roochAddress) + + return await provider.getFormattedAssets(runtime); + } catch (error) { + console.error("Error in assets provider:", error); + return error; + } + }, +}; diff --git a/packages/plugin-rooch/src/providers/sampleProvider.ts b/packages/plugin-rooch/src/providers/sampleProvider.ts deleted file mode 100644 index d16f3ba6dd..0000000000 --- a/packages/plugin-rooch/src/providers/sampleProvider.ts +++ /dev/null @@ -1,14 +0,0 @@ -import { - Provider, - IAgentRuntime, - Memory, - State, - elizaLogger, -} from "@elizaos/core"; - -export const sampleProvider: Provider = { - get: async (runtime: IAgentRuntime, message: Memory, state: State) => { - // Data retrieval logic for the provider - elizaLogger.log("Retrieving data in sampleProvider..."); - }, -}; diff --git a/packages/plugin-rooch/src/types.ts b/packages/plugin-rooch/src/types.ts index ef7d493963..d86713c3d7 100644 --- a/packages/plugin-rooch/src/types.ts +++ b/packages/plugin-rooch/src/types.ts @@ -53,3 +53,15 @@ export interface ExamplePluginConfig { apiSecret: string; endpoint?: string; } + + +export interface BTCUTXO { + utxo?: string; + sats?: string; +} + +export interface RoochCoin { + symbol: string; + name: string; + balance: number; +} \ No newline at end of file diff --git a/packages/plugin-rooch/src/utils.ts b/packages/plugin-rooch/src/utils.ts index abb8fec8fd..c0a2ec7c02 100644 --- a/packages/plugin-rooch/src/utils.ts +++ b/packages/plugin-rooch/src/utils.ts @@ -1,6 +1,6 @@ import { IAgentRuntime } from "@elizaos/core"; import bs58check from 'bs58check'; -import { ParsedKeypair } from "@roochnetwork/rooch-sdk/dist/esm"; +import { ParsedKeypair, Secp256k1Keypair, BitcoinAddress } from "@roochnetwork/rooch-sdk/dist/esm"; const parseKeypair = (runtime: IAgentRuntime): ParsedKeypair => { const wifPrivateKey = runtime.getSetting("BITCOIN_PRIVATE_KEY"); @@ -24,6 +24,12 @@ const parseKeypair = (runtime: IAgentRuntime): ParsedKeypair => { } }; +const parseBitcoinAddress = (runtime: IAgentRuntime): BitcoinAddress => { + const parsedKeypair = parseKeypair(runtime) + const keypair = Secp256k1Keypair.fromSecretKey(parsedKeypair.secretKey, false) + return keypair.getBitcoinAddress() +} + const parseAccessPath = (uri: string): string => { // Adjust the regex to ensure correct matching const match = uri.match(/^rooch:\/\/object\/(0x[a-fA-F0-9]+)$/); @@ -33,4 +39,4 @@ const parseAccessPath = (uri: string): string => { throw new Error("Invalid URI format"); }; -export { parseKeypair, parseAccessPath }; \ No newline at end of file +export { parseKeypair, parseBitcoinAddress, parseAccessPath }; \ No newline at end of file