Skip to main content

Beyond ElizaOS: Additional Functionality

We are working on implementing features that are not available in Eliza. Current functionality includes:

  • Get Account Balance on Fuse blockchain
  • Transfer FUSE token with user-connected wallet instead of hardcoded private key

Get Account Balance on Fuse blockchain

We have implemented a custom action “getBalance” in the ElizaOS to retrieve the correct information from the Fuse blockchain.

export const getBalanceAction = {
name: "getBalance",
description: "Get the balance of a provided wallet address",
handler: async (
runtime: IAgentRuntime,
_message: Memory,
state: State,
_options: any,
callback?: (response: any) => void
) => {
const walletProvider = initWalletProvider(runtime);
const currentChain = walletProvider.getCurrentChain().name.toLowerCase() as SupportedChain;
const { address } = await buildAddressDetails(state, runtime);

try {
const client = walletProvider.getPublicClient(currentChain);
const balance = await client.getBalance({ address });
const formattedBalance = formatEther(balance);

if (callback) {
callback({
text: `Balance for address ${address}: ${formattedBalance}`,
content: { balance: formattedBalance },
});
}
return true;
} catch (error) {
console.error("Error getting balance:", error);
if (callback) {
callback({
text: `Error getting balance: ${error.message}`,
content: { error: error.message },
});
}
return false;
}
},
template: getAddressTemplate,
validate: async (runtime: IAgentRuntime) => {
const privateKey = runtime.getSetting("EVM_PRIVATE_KEY");
return typeof privateKey === "string" && privateKey.startsWith("0x");
},
examples: [
[
{
user: "assistant",
content: {
text: "I'll check the balance for 0x742d35Cc6634C0532925a3b844Bc454e4438f44e",
action: "GET_BALANCE",
},
},
{
user: "user",
content: {
text: "What's the balance of 0x742d35Cc6634C0532925a3b844Bc454e4438f44e?",
action: "GET_BALANCE",
},
},
],
],
similes: ["CHECK_BALANCE", "BALANCE_INQUIRY", "ACCOUNT_BALANCE"],
};

Transfer FUSE token with user-connected wallet

In this instance, we have replaced Eliza’s hardcoded private key with the user’s connected wallet, enabling the user to transfer the FUSE token on the blockchain via Edison themselves.

ElizaOS/Backend:

export const transferAction = {
name: "transfer",
description: "Transfer tokens between addresses on the same chain",
handler: async (
runtime: IAgentRuntime,
_message: Memory,
state: State,
_options: any,
callback?: HandlerCallback
) => {
const transferDetails = await buildTransferDetails(state, runtime);

try {
if (callback) {
callback({
text: `You are about to transfer ${transferDetails.amount} to ${transferDetails.toAddress}`,
content: transferDetails,
action: "SEND_TOKENS"
});
}
return true;
} catch (error) {
console.error("Error during token transfer:", error);
if (callback) {
callback({
text: `Error transferring tokens: ${error.message}`,
content: { error: error.message },
});
}
return false;
}
},
template: transferTemplate,
validate: async (runtime: IAgentRuntime) => true,
examples: [
[
{
user: "assistant",
content: {
text: "I'll help you transfer 1 ETH to 0x742d35Cc6634C0532925a3b844Bc454e4438f44e",
action: "SEND_TOKENS",
},
},
{
user: "user",
content: {
text: "Transfer 1 ETH to 0x742d35Cc6634C0532925a3b844Bc454e4438f44e",
action: "SEND_TOKENS",
},
},
],
],
similes: ["SEND_TOKENS", "TOKEN_TRANSFER", "MOVE_TOKENS"],
};

Frontend: 

type ChatMessageProps = {
message: TextResponse;
setMessages: React.Dispatch<React.SetStateAction<TextResponse[]>>
};

type SendTokensContent = {
amount: string;
toAddress: Address;
}

const Spinner = () => {
return (
<span className="animate-spin border-2 border-gray border-t-2 border-t-dark-gray rounded-full w-4 h-4"></span>
)
}

const Button = ({ message, setMessages }: ChatMessageProps) => {
const dispatch = useAppDispatch();
const { isConnected } = useAccount();
const content = message.content as SendTokensContent;

const {
data: hash,
error,
isPending,
sendTransaction
} = useSendTransaction()

const { isLoading: isConfirming, isSuccess: isConfirmed } =
useWaitForTransactionReceipt({
hash,
})

const isLoading = isPending || isConfirming;

useEffect(() => {
if (isConfirmed && hash) {
setMessages((prevMessages) => {
const updatedMessages = [...prevMessages];
const lastSendTokensIndex = updatedMessages
.slice()
.reverse()
.findIndex((msg) => msg.text === message.text);

if (lastSendTokensIndex !== -1) {
const actualIndex = updatedMessages.length - 1 - lastSendTokensIndex;
updatedMessages[actualIndex] = {
...updatedMessages[actualIndex],
hash,
};
}

return updatedMessages as TextResponse[];
});
}
}, [hash, isConfirmed, message.text, setMessages]);

return (
<button
className={`transition ease-in-out flex items-center gap-2 w-fit px-4 py-3 text-lg leading-none font-semibold rounded-full hover:bg-[transparent] hover:text-black ${error?.message ? "bg-[#FFEBE9] border border-[#FD0F0F] text-[#FD0F0F]" : "bg-black border border-black text-white"}`}
onClick={() => {
if (message.hash) {
window.open(`${fuse.blockExplorers.default.url}/tx/${hash}`, "_blank");
} else if (isConnected) {
sendTransaction({
to: content.toAddress,
value: parseEther(content.amount),
});
} else {
dispatch(setIsWalletModalOpen(true));
}
}}
>
{message.hash ? "View on Explorer" : isConnected ? "Sign Transaction" : "Connect Wallet"}
{isLoading && <Spinner />}
{message.hash && <ExternalArrow />}
</button>
);
};

const ChatMessage = ({ message, setMessages }: ChatMessageProps) => {
const isUser = message.user === "user";
const isButton = message.action === "SEND_TOKENS";

return (
<div className={`flex gap-3 mb-4 ${isUser ? "justify-end" : ""}`}>
{!isUser && (
<div className="w-11 h-11 rounded-full overflow-hidden">
<Image src={edisonLogo} alt="AI Assistant" width={50} height={50} />
</div>
)}
<div
className={`max-w-[85%] p-4 flex flex-col gap-2 ${isUser
? "bg-[#E4E4E4] text-black rounded-l-[20px] rounded-br-[20px]"
: ""
}`}
>
<p className="text-sm">{message.text}</p>
{isButton && <Button message={message} setMessages={setMessages} />}
</div>
</div>
);
};

export default ChatMessage;