Skip to content

Commit

Permalink
Fixed all the typing errors, all the logical error, refactored the ac…
Browse files Browse the repository at this point in the history
…tion
  • Loading branch information
AIFlowML committed Jan 30, 2025
1 parent ca5d7cc commit c9adffd
Show file tree
Hide file tree
Showing 3 changed files with 130 additions and 75 deletions.
102 changes: 60 additions & 42 deletions packages/plugin-holdstation/src/actions/swapAction.ts
Original file line number Diff line number Diff line change
@@ -1,30 +1,47 @@
import {
Action,
IAgentRuntime,
Memory,
HandlerCallback,
State,
type Action,
type IAgentRuntime,
type Memory,
type HandlerCallback,
type State,
composeContext,
ModelClass,
elizaLogger,
ActionExample,
type ActionExample,
generateObjectDeprecated,
} from "@elizaos/core";

import { swapTemplate } from "../templates";
import { SendTransactionParams, SwapParams } from "../types";
import type { SendTransactionParams, SwapParams } from "../types";
import {
initWalletProvider,
WalletProvider,
type WalletProvider,
} from "../providers/walletProvider";
import { validateHoldStationConfig } from "../environment";
import { HOLDSTATION_ROUTER_ADDRESS, NATIVE_ADDRESS } from "../constants";
import { parseUnits } from "viem";
import { parseUnits, type Hex, type Address } from "viem";

// ------------------------------------------------------------------------------------------------
// Interfaces
// ------------------------------------------------------------------------------------------------
interface SwapResult {
hash: Hex;
inputTokenCA?: Address;
inputTokenSymbol?: string;
outputTokenCA?: Address;
outputTokenSymbol?: string;
amount: bigint;
slippage?: number;
text?: string;
}

// ------------------------------------------------------------------------------------------------
// Core Action Class
// ------------------------------------------------------------------------------------------------
export class SwapAction {
constructor(private walletProvider: WalletProvider) {}

async swap(params: SwapParams): Promise<any> {
async swap(params: SwapParams): Promise<SwapResult> {
const { items: tokens } = await this.walletProvider.fetchPortfolio();

if (!params.inputTokenCA && !params.inputTokenSymbol) {
Expand All @@ -36,7 +53,7 @@ export class SwapAction {
? t.address === params.inputTokenCA
: t.symbol === params.inputTokenSymbol?.toUpperCase();
});
if (filters.length != 1) {
if (filters.length !== 1) {
throw new Error(
"Multiple tokens or no tokens found with the symbol"
);
Expand All @@ -61,7 +78,7 @@ export class SwapAction {
? t.address === params.outputTokenCA
: t.symbol === params.outputTokenSymbol?.toUpperCase();
});
if (filters.length != 1) {
if (filters.length !== 1) {
throw new Error(
"Multiple tokens or no tokens found with the symbol"
);
Expand Down Expand Up @@ -127,8 +144,12 @@ export class SwapAction {
}
}

// ------------------------------------------------------------------------------------------------
// Core Action Implementation
// ------------------------------------------------------------------------------------------------
export const swapAction: Action = {
name: "TOKEN_SWAP_BY_HOLDSTATION",
description: "Perform swapping of tokens on ZKsync by HoldStation swap.",
similes: [
"SWAP_TOKEN",
"SWAP_TOKEN_BY_HOLDSTATION_SWAP",
Expand All @@ -137,33 +158,31 @@ export const swapAction: Action = {
"CONVERT_TOKENS",
"CONVERT_TOKENS_BY_HOLDSTATION_SWAP",
],
validate: async (runtime: IAgentRuntime, _message: Memory) => {
validate: async (runtime: IAgentRuntime, _message: Memory): Promise<boolean> => {
await validateHoldStationConfig(runtime);
return true;
},
description: "Perform swapping of tokens on ZKsync by HoldStation swap.",
handler: async (
runtime: IAgentRuntime,
message: Memory,
state: State,
_options: any,
callback: HandlerCallback
) => {
state?: State,
_options?: Record<string, unknown>,
callback?: HandlerCallback
): Promise<boolean> => {
elizaLogger.log("Starting HoldStation Wallet TOKEN_SWAP handler...");

const walletProvider = await initWalletProvider(runtime);
const action = new SwapAction(walletProvider);

// compose state
if (!state) {
state = (await runtime.composeState(message)) as State;
} else {
state = await runtime.updateRecentMessageState(state);
// Initialize or update state
let currentState = state ?? await runtime.composeState(message) as State;
if (state) {
currentState = await runtime.updateRecentMessageState(currentState);
}

// compose swap context
const swapContext = composeContext({
state,
state: currentState,
template: swapTemplate,
});

Expand All @@ -186,28 +205,27 @@ export const swapAction: Action = {
amount,
} = await action.swap(content);

elizaLogger.success(
`Swap completed successfully from ${amount} ${inputTokenSymbol} (${inputTokenCA}) to ${outputTokenSymbol} (${outputTokenCA})!\nTransaction Hash: ${hash}`
);
const successMessage = `Swap completed successfully from ${amount} ${inputTokenSymbol} (${inputTokenCA}) to ${outputTokenSymbol} (${outputTokenCA})!\nTransaction Hash: ${hash}`;
elizaLogger.success(successMessage);

if (callback) {
callback({
text: `Swap completed successfully from ${amount} ${inputTokenSymbol} (${inputTokenCA}) to ${outputTokenSymbol} (${outputTokenCA})!\nTransaction Hash: ${hash}`,
content: {
success: true,
hash: hash,
},
});
}
callback?.({
text: successMessage,
content: {
success: true,
hash: hash,
},
});

return true;
} catch (error) {
elizaLogger.error("Error during token swap:", error);
if (callback) {
callback({
text: `Error during token swap: ${error.message}`,
content: { error: error.message },
});
}
const errorMessage = error instanceof Error ? error.message : "Unknown error";

callback?.({
text: `Error during token swap: ${errorMessage}`,
content: { error: errorMessage },
});

return false;
}
},
Expand Down
7 changes: 5 additions & 2 deletions packages/plugin-holdstation/src/hooks/useGetAccount.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,9 @@ import type { PrivateKeyAccount } from "viem/accounts";
import { privateKeyToAccount } from "viem/accounts";

export const useGetAccount = (runtime: IAgentRuntime): PrivateKeyAccount => {
const PRIVATE_KEY = runtime.getSetting("HOLDSTATION_PRIVATE_KEY")!;
return privateKeyToAccount(`0x${PRIVATE_KEY}`);
const privateKey = runtime.getSetting("HOLDSTATION_PRIVATE_KEY");
if (!privateKey) {
throw new Error("HOLDSTATION_PRIVATE_KEY not found in settings");
}
return privateKeyToAccount(`0x${privateKey}`);
};
96 changes: 65 additions & 31 deletions packages/plugin-holdstation/src/providers/walletProvider.ts
Original file line number Diff line number Diff line change
@@ -1,44 +1,67 @@
import {
Provider,
IAgentRuntime,
Memory,
State,
type Provider,
type IAgentRuntime,
type Memory,
type State,
elizaLogger,
} from "@elizaos/core";

import {
Address,
type Address,
createPublicClient,
erc20Abi,
PublicClient,
type PublicClient,
http,
WalletClient,
HttpTransport,
Account,
Chain,
SendTransactionParameters,
type WalletClient,
type HttpTransport,
type Account,
type Chain,
type SendTransactionParameters,
type Hex,
} from "viem";
import { zksync } from "viem/chains";
import { PrivateKeyAccount } from "viem/accounts";
import type { PrivateKeyAccount } from "viem/accounts";

import { useGetAccount, useGetWalletClient } from "../hooks";
import { Item, SendTransactionParams, WalletPortfolio } from "../types";
import type { Item, SendTransactionParams, WalletPortfolio } from "../types";

import NodeCache from "node-cache";

// Add interface for portfolio API response
interface PortfolioItem {
contract_name: string;
contract_address: string;
contract_ticker_symbol: string;
contract_decimals: number;
}

// Add interface for token API response
interface TokenApiItem {
name: string;
address: string;
symbol: string;
decimals: number;
}

// Update interface to match the actual return type
interface HoldstationWalletResponse {
message: string;
error?: string;
}

export class WalletProvider {
private cache: NodeCache;
account: PrivateKeyAccount;
walletClient: WalletClient;
publicClient: PublicClient<HttpTransport, Chain, Account | undefined>;
publicClient: PublicClient<HttpTransport, typeof zksync, Account | undefined>;

constructor(account: PrivateKeyAccount) {
this.account = account;
this.walletClient = useGetWalletClient();
this.publicClient = createPublicClient<HttpTransport>({
this.publicClient = createPublicClient({
chain: zksync,
transport: http(),
}) as PublicClient<HttpTransport, Chain, Account | undefined>;
}) as PublicClient<HttpTransport, typeof zksync, Account | undefined>;
this.cache = new NodeCache({ stdTTL: 300 });
}

Expand All @@ -54,7 +77,7 @@ export class WalletProvider {
tokenAddress: Address,
owner: Address,
spender: Address
): Promise<any> {
): Promise<bigint> {
return this.publicClient.readContract({
address: tokenAddress,
abi: erc20Abi,
Expand All @@ -68,7 +91,7 @@ export class WalletProvider {
tokenAddress: Address,
amount: bigint
) {
const result = await this.walletClient.writeContract({
await this.walletClient.writeContract({
account: this.account,
address: tokenAddress,
abi: erc20Abi,
Expand All @@ -78,12 +101,14 @@ export class WalletProvider {
});
}

async sendTransaction(req: SendTransactionParams): Promise<any> {
async sendTransaction(req: SendTransactionParams): Promise<Hex> {
const txRequest: SendTransactionParameters = {
...req,
account: this.account,
chain: zksync,
kzg: undefined,
data: req.data ? (req.data as `0x${string}`) : undefined,
to: req.to ? (req.to as `0x${string}`) : undefined,
};
const tx = await this.walletClient.sendTransaction(txRequest);
console.log("sendTransaction txhash:", tx);
Expand Down Expand Up @@ -115,9 +140,11 @@ export class WalletProvider {

const items: Array<Item> =
portfolioData.data.map(
(item: any): Item => ({
(item: PortfolioItem): Item => ({
name: item.contract_name,
address: item.contract_address,
address: item.contract_address.startsWith('0x')
? item.contract_address as `0x${string}`
: `0x${item.contract_address}` as `0x${string}`,
symbol: item.contract_ticker_symbol,
decimals: item.contract_decimals,
})
Expand All @@ -134,15 +161,15 @@ export class WalletProvider {

async fetchAllTokens(): Promise<Array<Item>> {
try {
const cacheKey = `all-hswallet-tokens`;
const cacheKey = 'all-hswallet-tokens';
const cachedValue = this.cache.get<Array<Item>>(cacheKey);
if (cachedValue) {
elizaLogger.log("Cache hit for fetch all");
return cachedValue;
}
elizaLogger.log("Cache miss for fetch all");

const fetchUrl = `https://tokens.coingecko.com/zksync/all.json`;
const fetchUrl = 'https://tokens.coingecko.com/zksync/all.json';

const tokensResp = await fetch(fetchUrl);
const tokensData = await tokensResp.json();
Expand All @@ -157,9 +184,11 @@ export class WalletProvider {

const tokens: Array<Item> =
tokensData.tokens.map(
(item: any): Item => ({
(item: TokenApiItem): Item => ({
name: item.name,
address: item.address,
address: item.address.startsWith('0x')
? item.address as `0x${string}`
: `0x${item.address}` as `0x${string}`,
symbol: item.symbol,
decimals: item.decimals,
})
Expand All @@ -183,16 +212,21 @@ export const initWalletProvider = async (runtime: IAgentRuntime) => {
export const holdstationWalletProvider: Provider = {
get: async (
runtime: IAgentRuntime,
message: Memory,
state: State
): Promise<any> => {
_message: Memory,
state?: State
): Promise<HoldstationWalletResponse> => {
try {
const walletProvider = await initWalletProvider(runtime);
const agentName = state.agentName || "The agent";
return `${agentName}'s HoldStation Wallet address: ${walletProvider.getAddress()}`;
const agentName = state?.agentName || "The agent";
return {
message: `${agentName}'s HoldStation Wallet address: ${walletProvider.getAddress()}`
};
} catch (error) {
console.error("Error in HoldStation Wallet provider:", error);
return null;
return {
message: "Failed to get wallet address",
error: error instanceof Error ? error.message : "Unknown error"
};
}
},
};

0 comments on commit c9adffd

Please sign in to comment.