From 9b6479a6bff0ec6936621514b22d014f1af24acd Mon Sep 17 00:00:00 2001
From: MarcoMandar <malicemandar@gmail.com>
Date: Mon, 4 Nov 2024 12:07:31 +0200
Subject: [PATCH 1/8] swapDao

Signed-off-by: MarcoMandar <malicemandar@gmail.com>
---
 core/src/actions/swapDao.ts   | 168 ++++++++++++++++++++++++++++++++++
 core/src/actions/swapUtils.ts |   2 +
 core/src/providers/token.ts   |   6 +-
 3 files changed, 174 insertions(+), 2 deletions(-)
 create mode 100644 core/src/actions/swapDao.ts

diff --git a/core/src/actions/swapDao.ts b/core/src/actions/swapDao.ts
new file mode 100644
index 00000000000..9679137c121
--- /dev/null
+++ b/core/src/actions/swapDao.ts
@@ -0,0 +1,168 @@
+import { Connection, Keypair, PublicKey, Transaction } from "@solana/web3.js";
+import fetch from "cross-fetch";
+import {
+    ActionExample,
+    IAgentRuntime,
+    Memory,
+    type Action,
+} from "../core/types.ts";
+
+async function getQuote(
+    baseToken: string,
+    outputToken: string,
+    amount: number
+): Promise<any> {
+    const quoteResponse = await fetch(
+        `https://quote-api.jup.ag/v6/quote?inputMint=${baseToken}&outputMint=${outputToken}&amount=${amount * 10 ** 6}&slippageBps=50`
+    );
+    const swapTransaction = await quoteResponse.json();
+    const swapTransactionBuf = Buffer.from(swapTransaction, "base64");
+    return new Uint8Array(swapTransactionBuf);
+}
+
+async function invokeSwapDao(
+    connection: Connection,
+    authority: Keypair,
+    statePDA: PublicKey,
+    walletPDA: PublicKey,
+    instructionData: Buffer
+): Promise<string> {
+    const discriminator = new Uint8Array([
+        25, 143, 207, 190, 174, 228, 130, 107,
+    ]);
+
+    // Combine discriminator and instructionData into a single Uint8Array
+    const combinedData = new Uint8Array(
+        discriminator.length + instructionData.length
+    );
+    combinedData.set(discriminator, 0);
+    combinedData.set(instructionData, discriminator.length);
+
+    const transaction = new Transaction().add({
+        programId: new PublicKey("PROGRAM_ID"),
+        keys: [
+            { pubkey: authority.publicKey, isSigner: true, isWritable: true },
+            { pubkey: statePDA, isSigner: false, isWritable: true },
+            { pubkey: walletPDA, isSigner: false, isWritable: true },
+        ],
+        data: Buffer.from(combinedData),
+    });
+
+    const signature = await connection.sendTransaction(transaction, [
+        authority,
+    ]);
+    await connection.confirmTransaction(signature);
+    return signature;
+}
+
+async function promptConfirmation(): Promise<boolean> {
+    // confirmation logic here
+    const confirmSwap = window.confirm("Confirm the token swap?");
+    return confirmSwap;
+}
+
+export const executeSwap: Action = {
+    name: "EXECUTE_SWAP_DAO",
+    similes: ["SWAP_TOKENS_DAO", "TOKEN_SWAP_DAO"],
+    validate: async (runtime: IAgentRuntime, message: Memory) => {
+        console.log("Message:", message);
+        return true;
+    },
+    description: "Perform a DAO token swap using execute_invoke.",
+    handler: async (
+        runtime: IAgentRuntime,
+        message: Memory
+    ): Promise<boolean> => {
+        const { inputToken, outputToken, amount } = message.content;
+
+        try {
+            const connection = new Connection(
+                "https://api.mainnet-beta.solana.com" // better if we use a better rpc
+            );
+            const authority = Keypair.fromSecretKey(
+                Uint8Array.from(
+                    Buffer.from(
+                        runtime.getSetting("WALLET_PRIVATE_KEY"), // should be the authority private key
+                        "base64"
+                    )
+                )
+            );
+            const daoMint = new PublicKey(runtime.getSetting("DAO_MINT")); // DAO mint address
+
+            // Derive PDAs
+            const [statePDA] = await PublicKey.findProgramAddress(
+                [Buffer.from("state"), daoMint.toBuffer()],
+                authority.publicKey
+            );
+            const [walletPDA] = await PublicKey.findProgramAddress(
+                [Buffer.from("wallet"), daoMint.toBuffer()],
+                authority.publicKey
+            );
+
+            const quoteData = await getQuote(
+                inputToken as string,
+                outputToken as string,
+                amount as number
+            );
+            console.log("Swap Quote:", quoteData);
+
+            const confirmSwap = await promptConfirmation();
+            if (!confirmSwap) {
+                console.log("Swap canceled by user");
+                return false;
+            }
+
+            // Prepare instruction data for swap
+            const instructionData = Buffer.from(
+                JSON.stringify({
+                    quote: quoteData.data,
+                    userPublicKey: authority.publicKey.toString(),
+                    wrapAndUnwrapSol: true,
+                })
+            );
+
+            const txid = await invokeSwapDao(
+                connection,
+                authority,
+                statePDA,
+                walletPDA,
+                instructionData
+            );
+
+            console.log("DAO Swap completed successfully!");
+            console.log(`Transaction ID: ${txid}`);
+
+            return true;
+        } catch (error) {
+            console.error("Error during DAO token swap:", error);
+            return false;
+        }
+    },
+    examples: [
+        [
+            {
+                user: "{{user1}}",
+                content: {
+                    inputTokenSymbol: "SOL",
+                    outputTokenSymbol: "USDC",
+                    inputToken: "So11111111111111111111111111111111111111112",
+                    outputToken: "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v",
+                    amount: 0.1,
+                },
+            },
+            {
+                user: "{{user2}}",
+                content: {
+                    text: "Swapping 0.1 SOL for USDC using DAO...",
+                    action: "TOKEN_SWAP_DAO",
+                },
+            },
+            {
+                user: "{{user2}}",
+                content: {
+                    text: "DAO Swap completed successfully! Transaction ID: ...",
+                },
+            },
+        ],
+    ] as ActionExample[][],
+} as Action;
diff --git a/core/src/actions/swapUtils.ts b/core/src/actions/swapUtils.ts
index 1f025b3f6a7..ceceea7d26c 100644
--- a/core/src/actions/swapUtils.ts
+++ b/core/src/actions/swapUtils.ts
@@ -227,6 +227,7 @@ export const fetchBuyTransaction = async (
 
         // deserialize the transaction
         const swapTransactionBuf = Buffer.from(swapTransaction, "base64");
+        // @ts-expect-error ts-migrate(7006) FIXME: Parameter 'swapTransactionBuf' implicitly has an 'any' type.
         var transaction = VersionedTransaction.deserialize(swapTransactionBuf);
 
         // sign the transaction
@@ -273,6 +274,7 @@ export const fetchSellTransaction = async (
 
         // deserialize the transaction
         const swapTransactionBuf = Buffer.from(swapTransaction, "base64");
+        // @ts-expect-error ts-migrate(7006) FIXME: Parameter 'swapTransactionBuf' implicitly has an 'any' type.
         var transaction = VersionedTransaction.deserialize(swapTransactionBuf);
 
         // sign the transaction
diff --git a/core/src/providers/token.ts b/core/src/providers/token.ts
index d20aedaebae..be191b300e4 100644
--- a/core/src/providers/token.ts
+++ b/core/src/providers/token.ts
@@ -2,7 +2,7 @@ import { Connection } from "@solana/web3.js";
 // import fetch from "cross-fetch";
 import { IAgentRuntime, Memory, Provider, State } from "../core/types.ts";
 import settings from "../core/settings.ts";
-import { toBN, BN } from '../utils/bignumber.js';
+import { toBN } from "../utils/bignumber.js";
 import {
     ProcessedTokenData,
     TokenSecurityData,
@@ -620,7 +620,9 @@ export class TokenProvider {
             })
             .map((holder) => ({
                 holderAddress: holder.address,
-                balanceUsd: toBN(holder.balance).multipliedBy(tokenPriceUsd).toFixed(2),
+                balanceUsd: toBN(holder.balance)
+                    .multipliedBy(tokenPriceUsd)
+                    .toFixed(2),
             }));
 
         return highValueHolders;

From f8ffc4dd5bf93e8439cbb55c5e4ee2041194274c Mon Sep 17 00:00:00 2001
From: MarcoMandar <malicemandar@gmail.com>
Date: Mon, 4 Nov 2024 12:21:03 +0200
Subject: [PATCH 2/8] get the decimal value from the blockchain

Signed-off-by: MarcoMandar <malicemandar@gmail.com>
---
 core/src/actions/swapDao.ts | 30 +++++++++++++++++++++++++++++-
 1 file changed, 29 insertions(+), 1 deletion(-)

diff --git a/core/src/actions/swapDao.ts b/core/src/actions/swapDao.ts
index 9679137c121..07651320ebf 100644
--- a/core/src/actions/swapDao.ts
+++ b/core/src/actions/swapDao.ts
@@ -7,13 +7,40 @@ import {
     type Action,
 } from "../core/types.ts";
 
+async function getTokenDecimals(
+    connection: Connection,
+    mintAddress: string
+): Promise<number> {
+    const mintPublicKey = new PublicKey(mintAddress);
+    const tokenAccountInfo =
+        await connection.getParsedAccountInfo(mintPublicKey);
+
+    // Check if the data is parsed and contains the expected structure
+    if (
+        tokenAccountInfo.value &&
+        typeof tokenAccountInfo.value.data === "object" &&
+        "parsed" in tokenAccountInfo.value.data
+    ) {
+        const parsedInfo = tokenAccountInfo.value.data.parsed?.info;
+        if (parsedInfo && typeof parsedInfo.decimals === "number") {
+            return parsedInfo.decimals;
+        }
+    }
+
+    throw new Error("Unable to fetch token decimals");
+}
+
 async function getQuote(
+    connection: Connection,
     baseToken: string,
     outputToken: string,
     amount: number
 ): Promise<any> {
+    const decimals = await getTokenDecimals(connection, baseToken);
+    const adjustedAmount = amount * 10 ** decimals;
+
     const quoteResponse = await fetch(
-        `https://quote-api.jup.ag/v6/quote?inputMint=${baseToken}&outputMint=${outputToken}&amount=${amount * 10 ** 6}&slippageBps=50`
+        `https://quote-api.jup.ag/v6/quote?inputMint=${baseToken}&outputMint=${outputToken}&amount=${adjustedAmount}&slippageBps=50`
     );
     const swapTransaction = await quoteResponse.json();
     const swapTransactionBuf = Buffer.from(swapTransaction, "base64");
@@ -100,6 +127,7 @@ export const executeSwap: Action = {
             );
 
             const quoteData = await getQuote(
+                connection as Connection,
                 inputToken as string,
                 outputToken as string,
                 amount as number

From 57e2bb03e0796cd124c0186ca7c2bb694f25b84c Mon Sep 17 00:00:00 2001
From: moon <shawmakesmagic@gmail.com>
Date: Mon, 4 Nov 2024 03:27:35 -0800
Subject: [PATCH 3/8] marc swap by CA

---
 README.md                        |   2 +-
 core/.env.example                |   2 +-
 core/src/actions/swap.ts         | 294 ++++++++++++++++++++++++++-----
 core/src/actions/swapDao.ts      |  41 +----
 core/src/actions/swapUtils.ts    |  46 ++++-
 core/src/cli/index.ts            |   7 +-
 core/src/clients/direct/index.ts |  27 ++-
 core/src/core/generation.ts      |  36 ++++
 core/src/core/parsing.ts         |   3 +-
 core/src/index.ts                |   4 +-
 core/src/providers/token.ts      |   2 +-
 docs/docs/api/index.md           |   2 +-
 12 files changed, 364 insertions(+), 102 deletions(-)

diff --git a/README.md b/README.md
index c35d4fd431f..818abfb9f3e 100644
--- a/README.md
+++ b/README.md
@@ -102,7 +102,7 @@ XAI_MODEL=
 # For asking Claude stuff
 ANTHROPIC_API_KEY=
 
-WALLET_SECRET_KEY=EXAMPLE_WALLET_SECRET_KEY
+WALLET_PRIVATE_KEY=EXAMPLE_WALLET_PRIVATE_KEY
 WALLET_PUBLIC_KEY=EXAMPLE_WALLET_PUBLIC_KEY
 
 BIRDEYE_API_KEY=
diff --git a/core/.env.example b/core/.env.example
index dacbc26f9ef..a26bd8b87d6 100644
--- a/core/.env.example
+++ b/core/.env.example
@@ -30,7 +30,7 @@ XAI_MODEL=
 # For asking Claude stuff
 ANTHROPIC_API_KEY=
 
-WALLET_SECRET_KEY=EXAMPLE_WALLET_SECRET_KEY
+WALLET_PRIVATE_KEY=EXAMPLE_WALLET_PRIVATE_KEY
 WALLET_PUBLIC_KEY=EXAMPLE_WALLET_PUBLIC_KEY
 
 BIRDEYE_API_KEY=
diff --git a/core/src/actions/swap.ts b/core/src/actions/swap.ts
index a8540afb427..9cc480654f7 100644
--- a/core/src/actions/swap.ts
+++ b/core/src/actions/swap.ts
@@ -1,45 +1,130 @@
-import { Connection, Keypair, PublicKey, Transaction } from "@solana/web3.js";
+import { Connection, Keypair, PublicKey, Transaction, VersionedTransaction } from "@solana/web3.js";
 import fetch from "cross-fetch";
 import {
     ActionExample,
     IAgentRuntime,
     Memory,
     type Action,
+    State,
+    ModelClass,
+    HandlerCallback
 } from "../core/types.ts";
+import { walletProvider } from "../providers/wallet.ts";
+import { composeContext } from "../core/context.ts";
+import { generateObject, generateObjectArray } from "../core/generation.ts";
+import { getTokenDecimals } from "./swapUtils.ts";
+import settings from "../core/settings.ts";
+import { bs58 } from "@coral-xyz/anchor/dist/cjs/utils/bytes/index.js";
 
 async function swapToken(
     connection: Connection,
     walletPublicKey: PublicKey,
-    inputTokenSymbol: string,
-    outputTokenSymbol: string,
+    inputTokenCA: string,
+    outputTokenCA: string,
     amount: number
 ): Promise<any> {
-    const quoteResponse = await fetch(
-        `https://quote-api.jup.ag/v6/quote?inputMint=${inputTokenSymbol}&outputMint=${outputTokenSymbol}&amount=${amount * 10 ** 6}&slippageBps=50`
-    );
-    const quoteData = await quoteResponse.json();
-
-    const swapResponse = await fetch("https://quote-api.jup.ag/v6/swap", {
-        method: "POST",
-        headers: {
-            "Content-Type": "application/json",
-        },
-        body: JSON.stringify({
-            quoteResponse: quoteData.data,
+    try {
+        // Get the decimals for the input token
+        const decimals = inputTokenCA === settings.SOL_ADDRESS ? 9 : 
+            await getTokenDecimals(connection, inputTokenCA);
+
+        console.log("Decimals:", decimals);
+        
+        const adjustedAmount = amount * (10 ** decimals);
+
+        console.log("Fetching quote with params:", {
+            inputMint: inputTokenCA,
+            outputMint: outputTokenCA,
+            amount: adjustedAmount
+        });
+
+        const quoteResponse = await fetch(
+            `https://quote-api.jup.ag/v6/quote?inputMint=${inputTokenCA}&outputMint=${outputTokenCA}&amount=${adjustedAmount}&slippageBps=50`
+        );
+        const quoteData = await quoteResponse.json();
+
+        if (!quoteData || quoteData.error) {
+            console.error("Quote error:", quoteData);
+            throw new Error(`Failed to get quote: ${quoteData?.error || 'Unknown error'}`);
+        }
+
+        console.log("Quote received:", quoteData);
+
+        const swapRequestBody = {
+            quoteResponse: quoteData,
             userPublicKey: walletPublicKey.toString(),
             wrapAndUnwrapSol: true,
-        }),
-    });
+            computeUnitPriceMicroLamports: 1000,
+            dynamicComputeUnitLimit: true
+        };
+
+        console.log("Requesting swap with body:", swapRequestBody);
+
+        const swapResponse = await fetch("https://quote-api.jup.ag/v6/swap", {
+            method: "POST",
+            headers: {
+                "Content-Type": "application/json",
+            },
+            body: JSON.stringify(swapRequestBody)
+        });
+
+        const swapData = await swapResponse.json();
+
+        if (!swapData || !swapData.swapTransaction) {
+            console.error("Swap error:", swapData);
+            throw new Error(`Failed to get swap transaction: ${swapData?.error || 'No swap transaction returned'}`);
+        }
+
+        console.log("Swap transaction received");
+        return swapData;
 
-    return await swapResponse.json();
+    } catch (error) {
+        console.error("Error in swapToken:", error);
+        throw error;
+    }
 }
 
-async function promptConfirmation(): Promise<boolean> {
-    // Implement your own confirmation logic here
-    // This is just a placeholder example
-    const confirmSwap = window.confirm("Confirm the token swap?");
-    return confirmSwap;
+
+const swapTemplate = `Respond with a JSON markdown block containing only the extracted values. Use null for any values that cannot be determined.
+
+Example response:
+\`\`\`json
+{
+    "inputTokenSymbol": "SOL",
+    "outputTokenSymbol": "USDC", 
+    "inputTokenCA": "So11111111111111111111111111111111111111112",
+    "outputTokenCA": "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v",
+    "amount": 1.5
+}
+\`\`\`
+
+{{recentMessages}}
+
+Given the recent messages and wallet information below:
+
+{{walletInfo}}
+
+Extract the following information about the requested token swap:
+- Input token symbol (the token being sold)
+- Output token symbol (the token being bought) 
+- Input token contract address if provided
+- Output token contract address if provided
+- Amount to swap
+
+Respond with a JSON markdown block containing only the extracted values. Use null for any values that cannot be determined. The result should be a valid JSON object with the following schema:
+\`\`\`json
+{
+    "inputTokenSymbol": string | null,
+    "outputTokenSymbol": string | null, 
+    "inputTokenCA": string | null,
+    "outputTokenCA": string | null,
+    "amount": number | string | null
 }
+\`\`\``;
+
+// if we get the token symbol but not the CA, check walet for matching token, and if we have, get the CA for it
+
+// swapToken should took CA, not symbol
 
 export const executeSwap: Action = {
     name: "EXECUTE_SWAP",
@@ -52,10 +137,73 @@ export const executeSwap: Action = {
     description: "Perform a token swap.",
     handler: async (
         runtime: IAgentRuntime,
-        message: Memory
+        message: Memory,
+        state: State,
+        _options: { [key: string]: unknown },
+        callback?: HandlerCallback
     ): Promise<boolean> => {
-        const { inputTokenSymbol, outputTokenSymbol, amount } = message.content;
+        
+        // composeState
+        if (!state) {
+            state = (await runtime.composeState(message)) as State;
+        } else {
+            state = await runtime.updateRecentMessageState(state);
+        }
+
+        const walletInfo = await walletProvider.get(runtime, message, state);
+
+        state.walletInfo = walletInfo;
+
+        const swapContext = composeContext({
+            state,
+            template: swapTemplate,
+        });
 
+        const response = await generateObject({
+            runtime,
+            context: swapContext,
+            modelClass: ModelClass.LARGE,
+        });
+
+        console.log("Response:", response);
+
+        // Add SOL handling logic
+        if (response.inputTokenSymbol?.toUpperCase() === 'SOL') {
+            response.inputTokenCA = settings.SOL_ADDRESS;
+        }
+        if (response.outputTokenSymbol?.toUpperCase() === 'SOL') {
+            response.outputTokenCA = settings.SOL_ADDRESS;
+        }
+
+        // if both contract addresses are set, lets execute the swap
+        // TODO: try to resolve CA from symbol based on existing symbol in wallet
+        if (!response.inputTokenCA || !response.outputTokenCA) {
+            console.log("No contract addresses provided, skipping swap");
+            const responseMsg = {
+                text: "I need the contract addresses to perform the swap",
+            };
+            callback?.(responseMsg);
+            return true;
+        }
+
+        if (!response.amount) {
+            console.log("No amount provided, skipping swap");
+            const responseMsg = {
+                text: "I need the amount to perform the swap",
+            };
+            callback?.(responseMsg);
+            return true;
+        }
+
+        // TODO: if response amount is half, all, etc, semantically retrieve amount and return as number
+        if (!response.amount) {
+            console.log("Amount is not a number, skipping swap");
+            const responseMsg = {
+                text: "The amount must be a number",
+            };
+            callback?.(responseMsg);
+            return true;
+        }
         try {
             const connection = new Connection(
                 "https://api.mainnet-beta.solana.com"
@@ -64,40 +212,94 @@ export const executeSwap: Action = {
                 runtime.getSetting("WALLET_PUBLIC_KEY")
             );
 
+            console.log("Wallet Public Key:", walletPublicKey);
+            console.log("inputTokenSymbol:", response.inputTokenCA);
+            console.log("outputTokenSymbol:", response.outputTokenCA);
+            console.log("amount:", response.amount);
+
             const swapResult = await swapToken(
                 connection,
                 walletPublicKey,
-                inputTokenSymbol as string,
-                outputTokenSymbol as string,
-                amount as number
+                response.inputTokenCA as string,
+                response.outputTokenCA as string,
+                response.amount as number
             );
 
-            console.log("Swap Quote:");
-            console.log(swapResult.quote);
+            console.log("Deserializing transaction...");
+            const transactionBuf = Buffer.from(swapResult.swapTransaction, "base64");
+            const transaction = VersionedTransaction.deserialize(transactionBuf);
+            
+            console.log("Preparing to sign transaction...");
+            const privateKeyString = runtime.getSetting("WALLET_PRIVATE_KEY");
+            
+            // Handle different private key formats
+            let secretKey: Uint8Array;
+            try {
+                // First try to decode as base58
+                secretKey = bs58.decode(privateKeyString);
+            } catch (e) {
+                try {
+                    // If that fails, try base64
+                    secretKey = Uint8Array.from(Buffer.from(privateKeyString, 'base64'));
+                } catch (e2) {
+                    throw new Error('Invalid private key format');
+                }
+            }
 
-            const confirmSwap = await promptConfirmation();
-            if (!confirmSwap) {
-                console.log("Swap canceled by user");
-                return false;
+            // Verify the key length
+            if (secretKey.length !== 64) {
+                console.error("Invalid key length:", secretKey.length);
+                throw new Error(`Invalid private key length: ${secretKey.length}. Expected 64 bytes.`);
             }
 
-            const transaction = Transaction.from(
-                Buffer.from(swapResult.swapTransaction, "base64")
-            );
-            const privateKey = runtime.getSetting("WALLET_PRIVATE_KEY");
-            const keypair = Keypair.fromSecretKey(
-                Uint8Array.from(Buffer.from(privateKey, "base64"))
-            );
-            transaction.sign(keypair);
+            console.log("Creating keypair...");
+            const keypair = Keypair.fromSecretKey(secretKey);
+            
+            // Verify the public key matches what we expect
+            const expectedPublicKey = runtime.getSetting("WALLET_PUBLIC_KEY");
+            if (keypair.publicKey.toBase58() !== expectedPublicKey) {
+                throw new Error("Generated public key doesn't match expected public key");
+            }
 
-            const txid = await connection.sendRawTransaction(
-                transaction.serialize()
-            );
-            await connection.confirmTransaction(txid);
+            console.log("Signing transaction...");
+            transaction.sign([keypair]);
+
+            console.log("Sending transaction...");
+
+            const latestBlockhash = await connection.getLatestBlockhash();
+
+            const txid = await connection.sendTransaction(transaction, {
+                skipPreflight: false,
+                maxRetries: 3,
+                preflightCommitment: 'confirmed'
+            });
+            
+            console.log("Transaction sent:", txid);
+
+            // Confirm transaction using the blockhash
+            const confirmation = await connection.confirmTransaction({
+                signature: txid,
+                blockhash: latestBlockhash.blockhash,
+                lastValidBlockHeight: latestBlockhash.lastValidBlockHeight
+            }, 'confirmed');
+
+            if (confirmation.value.err) {
+                throw new Error(`Transaction failed: ${confirmation.value.err}`);
+            }
+
+            if (confirmation.value.err) {
+                throw new Error(`Transaction failed: ${confirmation.value.err}`);
+            }
 
             console.log("Swap completed successfully!");
             console.log(`Transaction ID: ${txid}`);
 
+            const responseMsg = {
+                text: `Swap completed successfully! Transaction ID: ${txid}`,
+            };
+
+            callback?.(responseMsg);
+            
             return true;
         } catch (error) {
             console.error("Error during token swap:", error);
diff --git a/core/src/actions/swapDao.ts b/core/src/actions/swapDao.ts
index 07651320ebf..18d58187b5d 100644
--- a/core/src/actions/swapDao.ts
+++ b/core/src/actions/swapDao.ts
@@ -6,46 +6,7 @@ import {
     Memory,
     type Action,
 } from "../core/types.ts";
-
-async function getTokenDecimals(
-    connection: Connection,
-    mintAddress: string
-): Promise<number> {
-    const mintPublicKey = new PublicKey(mintAddress);
-    const tokenAccountInfo =
-        await connection.getParsedAccountInfo(mintPublicKey);
-
-    // Check if the data is parsed and contains the expected structure
-    if (
-        tokenAccountInfo.value &&
-        typeof tokenAccountInfo.value.data === "object" &&
-        "parsed" in tokenAccountInfo.value.data
-    ) {
-        const parsedInfo = tokenAccountInfo.value.data.parsed?.info;
-        if (parsedInfo && typeof parsedInfo.decimals === "number") {
-            return parsedInfo.decimals;
-        }
-    }
-
-    throw new Error("Unable to fetch token decimals");
-}
-
-async function getQuote(
-    connection: Connection,
-    baseToken: string,
-    outputToken: string,
-    amount: number
-): Promise<any> {
-    const decimals = await getTokenDecimals(connection, baseToken);
-    const adjustedAmount = amount * 10 ** decimals;
-
-    const quoteResponse = await fetch(
-        `https://quote-api.jup.ag/v6/quote?inputMint=${baseToken}&outputMint=${outputToken}&amount=${adjustedAmount}&slippageBps=50`
-    );
-    const swapTransaction = await quoteResponse.json();
-    const swapTransactionBuf = Buffer.from(swapTransaction, "base64");
-    return new Uint8Array(swapTransactionBuf);
-}
+import { getQuote } from "./swapUtils.ts";
 
 async function invokeSwapDao(
     connection: Connection,
diff --git a/core/src/actions/swapUtils.ts b/core/src/actions/swapUtils.ts
index ceceea7d26c..8439b1a6914 100644
--- a/core/src/actions/swapUtils.ts
+++ b/core/src/actions/swapUtils.ts
@@ -26,6 +26,46 @@ export async function delayedCall<T>(
     return method(...args);
 }
 
+export async function getTokenDecimals(
+    connection: Connection,
+    mintAddress: string
+): Promise<number> {
+    const mintPublicKey = new PublicKey(mintAddress);
+    const tokenAccountInfo =
+        await connection.getParsedAccountInfo(mintPublicKey);
+
+    // Check if the data is parsed and contains the expected structure
+    if (
+        tokenAccountInfo.value &&
+        typeof tokenAccountInfo.value.data === "object" &&
+        "parsed" in tokenAccountInfo.value.data
+    ) {
+        const parsedInfo = tokenAccountInfo.value.data.parsed?.info;
+        if (parsedInfo && typeof parsedInfo.decimals === "number") {
+            return parsedInfo.decimals;
+        }
+    }
+
+    throw new Error("Unable to fetch token decimals");
+}
+
+export async function getQuote(
+    connection: Connection,
+    baseToken: string,
+    outputToken: string,
+    amount: number
+): Promise<any> {
+    const decimals = await getTokenDecimals(connection, baseToken);
+    const adjustedAmount = amount * 10 ** decimals;
+
+    const quoteResponse = await fetch(
+        `https://quote-api.jup.ag/v6/quote?inputMint=${baseToken}&outputMint=${outputToken}&amount=${adjustedAmount}&slippageBps=50`
+    );
+    const swapTransaction = await quoteResponse.json();
+    const swapTransactionBuf = Buffer.from(swapTransaction, "base64");
+    return new Uint8Array(swapTransactionBuf);
+}
+
 export const executeSwap = async (
     transaction: VersionedTransaction,
     type: "buy" | "sell"
@@ -227,8 +267,7 @@ export const fetchBuyTransaction = async (
 
         // deserialize the transaction
         const swapTransactionBuf = Buffer.from(swapTransaction, "base64");
-        // @ts-expect-error ts-migrate(7006) FIXME: Parameter 'swapTransactionBuf' implicitly has an 'any' type.
-        var transaction = VersionedTransaction.deserialize(swapTransactionBuf);
+        const transaction = VersionedTransaction.deserialize(swapTransactionBuf);
 
         // sign the transaction
         transaction.sign([wallet]);
@@ -274,8 +313,7 @@ export const fetchSellTransaction = async (
 
         // deserialize the transaction
         const swapTransactionBuf = Buffer.from(swapTransaction, "base64");
-        // @ts-expect-error ts-migrate(7006) FIXME: Parameter 'swapTransactionBuf' implicitly has an 'any' type.
-        var transaction = VersionedTransaction.deserialize(swapTransactionBuf);
+        const transaction = VersionedTransaction.deserialize(swapTransactionBuf);
 
         // sign the transaction
         transaction.sign([wallet]);
diff --git a/core/src/cli/index.ts b/core/src/cli/index.ts
index 953bd04adb4..5ecfd99cf28 100644
--- a/core/src/cli/index.ts
+++ b/core/src/cli/index.ts
@@ -166,7 +166,12 @@ export async function createDirectRuntime(
         modelProvider: character.modelProvider,
         evaluators: [],
         character,
-        providers: [Provider.timeProvider, Provider.boredomProvider],
+        providers: [
+            Provider.timeProvider,
+            Provider.boredomProvider,
+            character.settings?.secrets?.WALLET_PUBLIC_KEY &&
+                Provider.walletProvider,
+        ].filter(Boolean),
         actions: [
             ...defaultActions,
             // Custom actions
diff --git a/core/src/clients/direct/index.ts b/core/src/clients/direct/index.ts
index 8be31891ec1..cff77fc23a4 100644
--- a/core/src/clients/direct/index.ts
+++ b/core/src/clients/direct/index.ts
@@ -18,11 +18,11 @@ const upload = multer({ storage: multer.memoryStorage() });
 
 export const messageHandlerTemplate =
     // {{goals}}
-    //   `# Action Examples
-    // {{actionExamples}}
-    // (Action examples are for reference only. Do not use the information from them in your response.)
+    `# Action Examples
+{{actionExamples}}
+(Action examples are for reference only. Do not use the information from them in your response.)
 
-    `# Task: Generate dialog and actions for the character {{agentName}}.
+# Task: Generate dialog and actions for the character {{agentName}}.
 About {{agentName}}:
 {{bio}}
 {{lore}}
@@ -205,8 +205,25 @@ class DirectClient {
                     );
                     return;
                 }
+                
+                let message = null as Content | null;
+
+                const result = await runtime.processActions(
+                    memory,
+                    [responseMessage],
+                    state,
+                    async (newMessages) => {
+                        message = newMessages;
+                        return [memory];
+                    }
+                )
+
+                if (message) {
+                    res.json([message, response]);
+                } else {
+                    res.json([response]);
+                }
 
-                res.json(response);
             }
         );
 
diff --git a/core/src/core/generation.ts b/core/src/core/generation.ts
index 0c7b728c4da..817dec242d3 100644
--- a/core/src/core/generation.ts
+++ b/core/src/core/generation.ts
@@ -403,6 +403,42 @@ export async function generateTextArray({
     }
 }
 
+export async function generateObject({
+    runtime,
+    context,
+    modelClass,
+}: {
+    runtime: IAgentRuntime;
+    context: string;
+    modelClass: string;
+}): Promise<any> {
+    if (!context) {
+        prettyConsole.error("generateObject context is empty");
+        return null;
+    }
+    let retryDelay = 1000;
+
+    while (true) {
+        try {
+            // this is slightly different than generateObjectArray, in that we parse object, not object array
+            const response = await generateText({
+        runtime,
+        context,
+        modelClass,
+    });
+    const parsedResponse = parseJSONObjectFromText(response);
+    if (parsedResponse) {
+                return parsedResponse;
+            }
+        } catch (error) {
+            prettyConsole.error("Error in generateObject:", error);
+        }
+
+        await new Promise((resolve) => setTimeout(resolve, retryDelay));
+        retryDelay *= 2;
+    }
+}
+
 export async function generateObjectArray({
     runtime,
     context,
diff --git a/core/src/core/parsing.ts b/core/src/core/parsing.ts
index 5835e8ed547..1cf110868d3 100644
--- a/core/src/core/parsing.ts
+++ b/core/src/core/parsing.ts
@@ -2,10 +2,11 @@ const jsonBlockPattern = /```json\n([\s\S]*?)\n```/;
 
 export const messageCompletionFooter = `\nResponse format should be formatted in a JSON block like this:
 \`\`\`json
-{ "user": "{{agentName}}", "text": string, "action": string }
+{ "user": "{{agentName}}", "text": string, "action": "string" }
 \`\`\``;
 
 export const shouldRespondFooter = `The available options are [RESPOND], [IGNORE], or [STOP]. Choose the most appropriate option.
+If {{agentName}} is talking too much, you can choose [IGNORE]
 
 Your response must include one of the options.`;
 
diff --git a/core/src/index.ts b/core/src/index.ts
index f74ca175040..45d0997ec9c 100644
--- a/core/src/index.ts
+++ b/core/src/index.ts
@@ -88,7 +88,9 @@ function chat() {
         );
 
         const data = await response.json();
-        console.log(`${characters[0].name}: ${data.text}`);
+        for (const message of data) {
+            console.log(`${characters[0].name}: ${message.text}`);
+        }
         chat();
     });
 }
diff --git a/core/src/providers/token.ts b/core/src/providers/token.ts
index be191b300e4..615d5f22170 100644
--- a/core/src/providers/token.ts
+++ b/core/src/providers/token.ts
@@ -517,7 +517,7 @@ export class TokenProvider {
         const limit = 1000;
         let cursor;
         //HELIOUS_API_KEY needs to be added
-        const url = `https://mainnet.helius-rpc.com/?api-key=${settings.HELIOUS_API_KEY || ""}`;
+        const url = `https://mainnet.helius-rpc.com/?api-key=${settings.HELIUS_API_KEY || ""}`;
         console.log({ url });
 
         try {
diff --git a/docs/docs/api/index.md b/docs/docs/api/index.md
index 4c893594131..9d416ea06e7 100644
--- a/docs/docs/api/index.md
+++ b/docs/docs/api/index.md
@@ -101,7 +101,7 @@ XAI_MODEL=
 # For asking Claude stuff
 ANTHROPIC_API_KEY=
 
-WALLET_SECRET_KEY=EXAMPLE_WALLET_SECRET_KEY
+WALLET_PRIVATE_KEY=EXAMPLE_WALLET_PRIVATE_KEY
 WALLET_PUBLIC_KEY=EXAMPLE_WALLET_PUBLIC_KEY
 
 BIRDEYE_API_KEY=

From e4af25fbd64f64ff9387f48b1b39833727a4a05e Mon Sep 17 00:00:00 2001
From: MarcoMandar <malicemandar@gmail.com>
Date: Mon, 4 Nov 2024 14:32:31 +0200
Subject: [PATCH 4/8] update

Signed-off-by: MarcoMandar <malicemandar@gmail.com>
---
 core/src/actions/swapUtils.ts | 2 --
 1 file changed, 2 deletions(-)

diff --git a/core/src/actions/swapUtils.ts b/core/src/actions/swapUtils.ts
index ceceea7d26c..1f025b3f6a7 100644
--- a/core/src/actions/swapUtils.ts
+++ b/core/src/actions/swapUtils.ts
@@ -227,7 +227,6 @@ export const fetchBuyTransaction = async (
 
         // deserialize the transaction
         const swapTransactionBuf = Buffer.from(swapTransaction, "base64");
-        // @ts-expect-error ts-migrate(7006) FIXME: Parameter 'swapTransactionBuf' implicitly has an 'any' type.
         var transaction = VersionedTransaction.deserialize(swapTransactionBuf);
 
         // sign the transaction
@@ -274,7 +273,6 @@ export const fetchSellTransaction = async (
 
         // deserialize the transaction
         const swapTransactionBuf = Buffer.from(swapTransaction, "base64");
-        // @ts-expect-error ts-migrate(7006) FIXME: Parameter 'swapTransactionBuf' implicitly has an 'any' type.
         var transaction = VersionedTransaction.deserialize(swapTransactionBuf);
 
         // sign the transaction

From 50f3a4d9bd4b23ced8964cd96d30e0cd41acf4ef Mon Sep 17 00:00:00 2001
From: MarcoMandar <malicemandar@gmail.com>
Date: Mon, 4 Nov 2024 15:34:01 +0200
Subject: [PATCH 5/8] use big number

Signed-off-by: MarcoMandar <malicemandar@gmail.com>
---
 core/src/actions/swap.ts | 107 +++++++++++++++++++++++++--------------
 1 file changed, 70 insertions(+), 37 deletions(-)

diff --git a/core/src/actions/swap.ts b/core/src/actions/swap.ts
index 9cc480654f7..3d75c56094e 100644
--- a/core/src/actions/swap.ts
+++ b/core/src/actions/swap.ts
@@ -1,4 +1,10 @@
-import { Connection, Keypair, PublicKey, Transaction, VersionedTransaction } from "@solana/web3.js";
+import {
+    Connection,
+    Keypair,
+    PublicKey,
+    Transaction,
+    VersionedTransaction,
+} from "@solana/web3.js";
 import fetch from "cross-fetch";
 import {
     ActionExample,
@@ -7,7 +13,7 @@ import {
     type Action,
     State,
     ModelClass,
-    HandlerCallback
+    HandlerCallback,
 } from "../core/types.ts";
 import { walletProvider } from "../providers/wallet.ts";
 import { composeContext } from "../core/context.ts";
@@ -15,6 +21,7 @@ import { generateObject, generateObjectArray } from "../core/generation.ts";
 import { getTokenDecimals } from "./swapUtils.ts";
 import settings from "../core/settings.ts";
 import { bs58 } from "@coral-xyz/anchor/dist/cjs/utils/bytes/index.js";
+import BigNumber from "bignumber.js";
 
 async function swapToken(
     connection: Connection,
@@ -25,17 +32,25 @@ async function swapToken(
 ): Promise<any> {
     try {
         // Get the decimals for the input token
-        const decimals = inputTokenCA === settings.SOL_ADDRESS ? 9 : 
-            await getTokenDecimals(connection, inputTokenCA);
-
-        console.log("Decimals:", decimals);
-        
-        const adjustedAmount = amount * (10 ** decimals);
+        const decimals =
+            inputTokenCA === settings.SOL_ADDRESS
+                ? new BigNumber(9)
+                : new BigNumber(
+                      await getTokenDecimals(connection, inputTokenCA)
+                  );
+
+        console.log("Decimals:", decimals.toString());
+
+        // Use BigNumber for adjustedAmount: amount * (10 ** decimals)
+        const amountBN = new BigNumber(amount);
+        const adjustedAmount = amountBN.multipliedBy(
+            new BigNumber(10).pow(decimals)
+        );
 
         console.log("Fetching quote with params:", {
             inputMint: inputTokenCA,
             outputMint: outputTokenCA,
-            amount: adjustedAmount
+            amount: adjustedAmount,
         });
 
         const quoteResponse = await fetch(
@@ -45,7 +60,9 @@ async function swapToken(
 
         if (!quoteData || quoteData.error) {
             console.error("Quote error:", quoteData);
-            throw new Error(`Failed to get quote: ${quoteData?.error || 'Unknown error'}`);
+            throw new Error(
+                `Failed to get quote: ${quoteData?.error || "Unknown error"}`
+            );
         }
 
         console.log("Quote received:", quoteData);
@@ -55,7 +72,7 @@ async function swapToken(
             userPublicKey: walletPublicKey.toString(),
             wrapAndUnwrapSol: true,
             computeUnitPriceMicroLamports: 1000,
-            dynamicComputeUnitLimit: true
+            dynamicComputeUnitLimit: true,
         };
 
         console.log("Requesting swap with body:", swapRequestBody);
@@ -65,26 +82,26 @@ async function swapToken(
             headers: {
                 "Content-Type": "application/json",
             },
-            body: JSON.stringify(swapRequestBody)
+            body: JSON.stringify(swapRequestBody),
         });
 
         const swapData = await swapResponse.json();
 
         if (!swapData || !swapData.swapTransaction) {
             console.error("Swap error:", swapData);
-            throw new Error(`Failed to get swap transaction: ${swapData?.error || 'No swap transaction returned'}`);
+            throw new Error(
+                `Failed to get swap transaction: ${swapData?.error || "No swap transaction returned"}`
+            );
         }
 
         console.log("Swap transaction received");
         return swapData;
-
     } catch (error) {
         console.error("Error in swapToken:", error);
         throw error;
     }
 }
 
-
 const swapTemplate = `Respond with a JSON markdown block containing only the extracted values. Use null for any values that cannot be determined.
 
 Example response:
@@ -142,7 +159,6 @@ export const executeSwap: Action = {
         _options: { [key: string]: unknown },
         callback?: HandlerCallback
     ): Promise<boolean> => {
-        
         // composeState
         if (!state) {
             state = (await runtime.composeState(message)) as State;
@@ -168,10 +184,10 @@ export const executeSwap: Action = {
         console.log("Response:", response);
 
         // Add SOL handling logic
-        if (response.inputTokenSymbol?.toUpperCase() === 'SOL') {
+        if (response.inputTokenSymbol?.toUpperCase() === "SOL") {
             response.inputTokenCA = settings.SOL_ADDRESS;
         }
-        if (response.outputTokenSymbol?.toUpperCase() === 'SOL') {
+        if (response.outputTokenSymbol?.toUpperCase() === "SOL") {
             response.outputTokenCA = settings.SOL_ADDRESS;
         }
 
@@ -226,12 +242,16 @@ export const executeSwap: Action = {
             );
 
             console.log("Deserializing transaction...");
-            const transactionBuf = Buffer.from(swapResult.swapTransaction, "base64");
-            const transaction = VersionedTransaction.deserialize(transactionBuf);
-            
+            const transactionBuf = Buffer.from(
+                swapResult.swapTransaction,
+                "base64"
+            );
+            const transaction =
+                VersionedTransaction.deserialize(transactionBuf);
+
             console.log("Preparing to sign transaction...");
             const privateKeyString = runtime.getSetting("WALLET_PRIVATE_KEY");
-            
+
             // Handle different private key formats
             let secretKey: Uint8Array;
             try {
@@ -240,25 +260,31 @@ export const executeSwap: Action = {
             } catch (e) {
                 try {
                     // If that fails, try base64
-                    secretKey = Uint8Array.from(Buffer.from(privateKeyString, 'base64'));
+                    secretKey = Uint8Array.from(
+                        Buffer.from(privateKeyString, "base64")
+                    );
                 } catch (e2) {
-                    throw new Error('Invalid private key format');
+                    throw new Error("Invalid private key format");
                 }
             }
 
             // Verify the key length
             if (secretKey.length !== 64) {
                 console.error("Invalid key length:", secretKey.length);
-                throw new Error(`Invalid private key length: ${secretKey.length}. Expected 64 bytes.`);
+                throw new Error(
+                    `Invalid private key length: ${secretKey.length}. Expected 64 bytes.`
+                );
             }
 
             console.log("Creating keypair...");
             const keypair = Keypair.fromSecretKey(secretKey);
-            
+
             // Verify the public key matches what we expect
             const expectedPublicKey = runtime.getSetting("WALLET_PUBLIC_KEY");
             if (keypair.publicKey.toBase58() !== expectedPublicKey) {
-                throw new Error("Generated public key doesn't match expected public key");
+                throw new Error(
+                    "Generated public key doesn't match expected public key"
+                );
             }
 
             console.log("Signing transaction...");
@@ -271,24 +297,31 @@ export const executeSwap: Action = {
             const txid = await connection.sendTransaction(transaction, {
                 skipPreflight: false,
                 maxRetries: 3,
-                preflightCommitment: 'confirmed'
+                preflightCommitment: "confirmed",
             });
-            
+
             console.log("Transaction sent:", txid);
 
             // Confirm transaction using the blockhash
-            const confirmation = await connection.confirmTransaction({
-                signature: txid,
-                blockhash: latestBlockhash.blockhash,
-                lastValidBlockHeight: latestBlockhash.lastValidBlockHeight
-            }, 'confirmed');
+            const confirmation = await connection.confirmTransaction(
+                {
+                    signature: txid,
+                    blockhash: latestBlockhash.blockhash,
+                    lastValidBlockHeight: latestBlockhash.lastValidBlockHeight,
+                },
+                "confirmed"
+            );
 
             if (confirmation.value.err) {
-                throw new Error(`Transaction failed: ${confirmation.value.err}`);
+                throw new Error(
+                    `Transaction failed: ${confirmation.value.err}`
+                );
             }
 
             if (confirmation.value.err) {
-                throw new Error(`Transaction failed: ${confirmation.value.err}`);
+                throw new Error(
+                    `Transaction failed: ${confirmation.value.err}`
+                );
             }
 
             console.log("Swap completed successfully!");
@@ -299,7 +332,7 @@ export const executeSwap: Action = {
             };
 
             callback?.(responseMsg);
-            
+
             return true;
         } catch (error) {
             console.error("Error during token swap:", error);

From fe0410e385d52e1a64d00c17af832ddfa551aeda Mon Sep 17 00:00:00 2001
From: MarcoMandar <malicemandar@gmail.com>
Date: Mon, 4 Nov 2024 16:43:40 +0200
Subject: [PATCH 6/8] add getTokensInWallet and add the logic  to resolve CA
 from symbol based on existing symbol in wallet

Signed-off-by: MarcoMandar <malicemandar@gmail.com>
---
 core/src/actions/swap.ts | 76 ++++++++++++++++++++++++++++++++++++----
 1 file changed, 69 insertions(+), 7 deletions(-)

diff --git a/core/src/actions/swap.ts b/core/src/actions/swap.ts
index 3d75c56094e..66f5c5fbd2c 100644
--- a/core/src/actions/swap.ts
+++ b/core/src/actions/swap.ts
@@ -22,6 +22,7 @@ import { getTokenDecimals } from "./swapUtils.ts";
 import settings from "../core/settings.ts";
 import { bs58 } from "@coral-xyz/anchor/dist/cjs/utils/bytes/index.js";
 import BigNumber from "bignumber.js";
+import { WalletProvider } from "../providers/wallet.ts";
 
 async function swapToken(
     connection: Connection,
@@ -141,6 +142,34 @@ Respond with a JSON markdown block containing only the extracted values. Use nul
 
 // if we get the token symbol but not the CA, check walet for matching token, and if we have, get the CA for it
 
+// get all the tokens in the wallet using the wallet provider
+async function getTokensInWallet(runtime: IAgentRuntime) {
+    const walletProvider = new WalletProvider(
+        new Connection("https://api.mainnet-beta.solana.com"),
+        new PublicKey(runtime.getSetting("WALLET_PUBLIC_KEY"))
+    );
+    const walletInfo = await walletProvider.fetchPortfolioValue(runtime);
+    const items = walletInfo.items;
+    return items;
+}
+
+// check if the token symbol is in the wallet
+async function checkTokenInWallet(runtime: IAgentRuntime, tokenSymbol: string) {
+    try {
+        const items = await getTokensInWallet(runtime);
+        const token = items.find((item) => item.symbol === tokenSymbol);
+
+        if (token) {
+            return token.address;
+        } else {
+            return null;
+        }
+    } catch (error) {
+        console.error("Error checking token in wallet:", error);
+        return null;
+    }
+}
+
 // swapToken should took CA, not symbol
 
 export const executeSwap: Action = {
@@ -193,13 +222,46 @@ export const executeSwap: Action = {
 
         // if both contract addresses are set, lets execute the swap
         // TODO: try to resolve CA from symbol based on existing symbol in wallet
-        if (!response.inputTokenCA || !response.outputTokenCA) {
-            console.log("No contract addresses provided, skipping swap");
-            const responseMsg = {
-                text: "I need the contract addresses to perform the swap",
-            };
-            callback?.(responseMsg);
-            return true;
+        if (!response.inputTokenCA && response.inputTokenSymbol) {
+            console.log(
+                `Attempting to resolve CA for input token symbol: ${response.inputTokenSymbol}`
+            );
+            response.inputTokenCA = await checkTokenInWallet(
+                runtime,
+                response.inputTokenSymbol
+            );
+            if (response.inputTokenCA) {
+                console.log(`Resolved inputTokenCA: ${response.inputTokenCA}`);
+            } else {
+                console.log("No contract addresses provided, skipping swap");
+                const responseMsg = {
+                    text: "I need the contract addresses to perform the swap",
+                };
+                callback?.(responseMsg);
+                return true;
+            }
+        }
+
+        if (!response.outputTokenCA && response.outputTokenSymbol) {
+            console.log(
+                `Attempting to resolve CA for output token symbol: ${response.outputTokenSymbol}`
+            );
+            response.outputTokenCA = await checkTokenInWallet(
+                runtime,
+                response.outputTokenSymbol
+            );
+            if (response.outputTokenCA) {
+                console.log(
+                    `Resolved outputTokenCA: ${response.outputTokenCA}`
+                );
+            } else {
+                console.log("No contract addresses provided, skipping swap");
+                const responseMsg = {
+                    text: "I need the contract addresses to perform the swap",
+                };
+                callback?.(responseMsg);
+                return true;
+            }
         }
 
         if (!response.amount) {

From 7e85609c07a7c5c6c96a224b455d658f30ae76a8 Mon Sep 17 00:00:00 2001
From: MarcoMandar <malicemandar@gmail.com>
Date: Mon, 4 Nov 2024 16:53:32 +0200
Subject: [PATCH 7/8] fix

Signed-off-by: MarcoMandar <malicemandar@gmail.com>
---
 core/src/actions/swap.ts     | 1 +
 core/src/providers/wallet.ts | 3 ++-
 2 files changed, 3 insertions(+), 1 deletion(-)

diff --git a/core/src/actions/swap.ts b/core/src/actions/swap.ts
index 66f5c5fbd2c..978c13a046f 100644
--- a/core/src/actions/swap.ts
+++ b/core/src/actions/swap.ts
@@ -148,6 +148,7 @@ async function getTokensInWallet(runtime: IAgentRuntime) {
         new Connection("https://api.mainnet-beta.solana.com"),
         new PublicKey(runtime.getSetting("WALLET_PUBLIC_KEY"))
     );
+
     const walletInfo = await walletProvider.fetchPortfolioValue(runtime);
     const items = walletInfo.items;
     return items;
diff --git a/core/src/providers/wallet.ts b/core/src/providers/wallet.ts
index a0a6ff08b89..3c9114c4009 100644
--- a/core/src/providers/wallet.ts
+++ b/core/src/providers/wallet.ts
@@ -18,6 +18,7 @@ const PROVIDER_CONFIG = {
 
 interface Item {
     name: string;
+    address: string;
     symbol: string;
     decimals: number;
     balance: string;
@@ -48,7 +49,7 @@ interface Prices {
     ethereum: { usd: string };
 }
 
-class WalletProvider {
+export class WalletProvider {
     constructor(
         private connection: Connection,
         private walletPublicKey: PublicKey

From 0b167441086fe5c09a250fba1685ef522a6b6691 Mon Sep 17 00:00:00 2001
From: MarcoMandar <malicemandar@gmail.com>
Date: Tue, 5 Nov 2024 02:51:25 +0200
Subject: [PATCH 8/8] recommender, token performane integration to swap

Signed-off-by: MarcoMandar <malicemandar@gmail.com>
---
 core/src/actions/swap.ts                 |  75 ++++++++++++++-
 core/src/adapters/trustScoreDatabase.ts  | 111 ++++++++++++++++++++++-
 core/src/providers/trustScoreProvider.ts |   7 +-
 3 files changed, 184 insertions(+), 9 deletions(-)

diff --git a/core/src/actions/swap.ts b/core/src/actions/swap.ts
index 978c13a046f..6a5a72e3093 100644
--- a/core/src/actions/swap.ts
+++ b/core/src/actions/swap.ts
@@ -23,6 +23,10 @@ import settings from "../core/settings.ts";
 import { bs58 } from "@coral-xyz/anchor/dist/cjs/utils/bytes/index.js";
 import BigNumber from "bignumber.js";
 import { WalletProvider } from "../providers/wallet.ts";
+import { TrustScoreProvider } from "../providers/trustScoreProvider";
+import { TokenProvider } from "../providers/token";
+import { TrustScoreDatabase } from "../adapters/trustScoreDatabase";
+import { v4 as uuidv4 } from "uuid";
 
 async function swapToken(
     connection: Connection,
@@ -155,7 +159,7 @@ async function getTokensInWallet(runtime: IAgentRuntime) {
 }
 
 // check if the token symbol is in the wallet
-async function checkTokenInWallet(runtime: IAgentRuntime, tokenSymbol: string) {
+async function getTokenFromWallet(runtime: IAgentRuntime, tokenSymbol: string) {
     try {
         const items = await getTokensInWallet(runtime);
         const token = items.find((item) => item.symbol === tokenSymbol);
@@ -212,6 +216,8 @@ export const executeSwap: Action = {
         });
 
         console.log("Response:", response);
+        const type =
+            response.inputTokenSymbol?.toUpperCase() === "SOL" ? "buy" : "sell";
 
         // Add SOL handling logic
         if (response.inputTokenSymbol?.toUpperCase() === "SOL") {
@@ -227,7 +233,7 @@ export const executeSwap: Action = {
             console.log(
                 `Attempting to resolve CA for input token symbol: ${response.inputTokenSymbol}`
             );
-            response.inputTokenCA = await checkTokenInWallet(
+            response.inputTokenCA = await getTokenFromWallet(
                 runtime,
                 response.inputTokenSymbol
             );
@@ -247,7 +253,7 @@ export const executeSwap: Action = {
             console.log(
                 `Attempting to resolve CA for output token symbol: ${response.outputTokenSymbol}`
             );
-            response.outputTokenCA = await checkTokenInWallet(
+            response.outputTokenCA = await getTokenFromWallet(
                 runtime,
                 response.outputTokenSymbol
             );
@@ -387,6 +393,69 @@ export const executeSwap: Action = {
                 );
             }
 
+            if (type === "buy") {
+                const tokenProvider = new TokenProvider(response.outputTokenCA);
+                const module = await import("better-sqlite3");
+                const Database = module.default;
+                const trustScoreDb = new TrustScoreDatabase(
+                    new Database(":memory:")
+                );
+                // add or get recommender
+                const uuid = uuidv4();
+                const recommender = await trustScoreDb.getOrCreateRecommender({
+                    id: uuid,
+                    address: walletPublicKey.toString(),
+                    solanaPubkey: walletPublicKey.toString(),
+                });
+
+                const trustScoreDatabase = new TrustScoreProvider(
+                    tokenProvider,
+                    trustScoreDb
+                );
+                // save the trade
+                const tradeData = {
+                    buy_amount: response.amount,
+                    is_simulation: false,
+                };
+                await trustScoreDatabase.createTradePerformance(
+                    response.outputTokenCA,
+                    recommender.id,
+                    tradeData
+                );
+            } else if (type === "sell") {
+                const tokenProvider = new TokenProvider(response.inputTokenCA);
+                const module = await import("better-sqlite3");
+                const Database = module.default;
+                const trustScoreDb = new TrustScoreDatabase(
+                    new Database(":memory:")
+                );
+                // add or get recommender
+                const uuid = uuidv4();
+                const recommender = await trustScoreDb.getOrCreateRecommender({
+                    id: uuid,
+                    address: walletPublicKey.toString(),
+                    solanaPubkey: walletPublicKey.toString(),
+                });
+
+                const trustScoreDatabase = new TrustScoreProvider(
+                    tokenProvider,
+                    trustScoreDb
+                );
+                // save the trade
+                const sellDetails = {
+                    sell_amount: response.amount,
+                    sell_recommender_id: recommender.id,
+                };
+                const sellTimeStamp = new Date().getTime().toString();
+                await trustScoreDatabase.updateSellDetails(
+                    response.inputTokenCA,
+                    recommender.id,
+                    sellTimeStamp,
+                    sellDetails,
+                    false
+                );
+            }
+
             console.log("Swap completed successfully!");
             console.log(`Transaction ID: ${txid}`);
 
diff --git a/core/src/adapters/trustScoreDatabase.ts b/core/src/adapters/trustScoreDatabase.ts
index b206fd1923e..5ef6231f5e0 100644
--- a/core/src/adapters/trustScoreDatabase.ts
+++ b/core/src/adapters/trustScoreDatabase.ts
@@ -172,7 +172,7 @@ export class TrustScoreDatabase {
                 risk_score REAL DEFAULT 0,
                 consistency_score REAL DEFAULT 0,
                 virtual_confidence REAL DEFAULT 0,
-                last_active_date DATETIME DEFAULT CURRENT_TIMESTAMP
+                last_active_date DATETIME DEFAULT CURRENT_TIMESTAMP,
                 trust_decay REAL DEFAULT 0,
                 last_updated DATETIME DEFAULT CURRENT_TIMESTAMP,
                 FOREIGN KEY (recommender_id) REFERENCES recommenders(id) ON DELETE CASCADE
@@ -349,6 +349,61 @@ export class TrustScoreDatabase {
         return recommender || null;
     }
 
+    /**
+     * Retrieves an existing recommender or creates a new one if not found.
+     * Also initializes metrics for the recommender if they haven't been initialized yet.
+     * @param recommender Recommender object containing at least one identifier
+     * @returns Recommender object with all details, or null if failed
+     */
+    getOrCreateRecommender(recommender: Recommender): Recommender | null {
+        try {
+            // Begin a transaction
+            const transaction = this.db.transaction(() => {
+                // Attempt to retrieve the recommender
+                const existingRecommender = this.getRecommender(
+                    recommender.address
+                );
+                if (existingRecommender) {
+                    // Recommender exists, ensure metrics are initialized
+                    this.initializeRecommenderMetrics(existingRecommender.id!);
+                    return existingRecommender;
+                }
+
+                // Recommender does not exist, create a new one
+                const newRecommenderId = this.addRecommender(recommender);
+                if (!newRecommenderId) {
+                    throw new Error("Failed to add new recommender.");
+                }
+
+                // Initialize metrics for the new recommender
+                const metricsInitialized =
+                    this.initializeRecommenderMetrics(newRecommenderId);
+                if (!metricsInitialized) {
+                    throw new Error(
+                        "Failed to initialize recommender metrics."
+                    );
+                }
+
+                // Retrieve and return the newly created recommender
+                const newRecommender = this.getRecommender(newRecommenderId);
+                if (!newRecommender) {
+                    throw new Error(
+                        "Failed to retrieve the newly created recommender."
+                    );
+                }
+
+                return newRecommender;
+            });
+
+            // Execute the transaction and return the recommender
+            const recommenderResult = transaction();
+            return recommenderResult;
+        } catch (error) {
+            console.error("Error in getOrCreateRecommender:", error);
+            return null;
+        }
+    }
+
     /**
      * Initializes metrics for a recommender if not present.
      * @param recommenderId Recommender's UUID
@@ -548,6 +603,8 @@ export class TrustScoreDatabase {
                 performance.tokenAddress,
                 performance.priceChange24h,
                 performance.volumeChange24h,
+                performance.trade_24h_change,
+                performance.liquidity,
                 performance.liquidityChange24h,
                 performance.holderChange24h, // Ensure column name matches schema
                 performance.rugPull ? 1 : 0,
@@ -916,7 +973,7 @@ export class TrustScoreDatabase {
             market_cap_change = ?,
             sell_liquidity = ?,
             liquidity_change = ?,
-            rapidDump = ?
+            rapidDump = ?,
             sell_recommender_id = ?
         WHERE
             token_address = ?
@@ -1016,6 +1073,56 @@ export class TrustScoreDatabase {
         };
     }
 
+    /**
+     * Retrieves the latest trade performance metrics without requiring buyTimeStamp.
+     * @param tokenAddress Token's address
+     * @param recommenderId Recommender's UUID
+     * @param isSimulation Whether the trade is a simulation. If true, retrieves from simulation_trade; otherwise, from trade.
+     * @returns TradePerformance object or null
+     */
+    getLatestTradePerformance(
+        tokenAddress: string,
+        recommenderId: string,
+        isSimulation: boolean
+    ): TradePerformance | null {
+        const tableName = isSimulation ? "simulation_trade" : "trade";
+        const sql = `
+        SELECT * FROM ${tableName}
+        WHERE token_address = ? AND recommender_id = ?
+        ORDER BY buy_timeStamp DESC
+        LIMIT 1;
+    `;
+        const row = this.db.prepare(sql).get(tokenAddress, recommenderId) as
+            | TradePerformance
+            | undefined;
+        if (!row) return null;
+
+        return {
+            token_address: row.token_address,
+            recommender_id: row.recommender_id,
+            buy_price: row.buy_price,
+            sell_price: row.sell_price,
+            buy_timeStamp: row.buy_timeStamp,
+            sell_timeStamp: row.sell_timeStamp,
+            buy_amount: row.buy_amount,
+            sell_amount: row.sell_amount,
+            buy_sol: row.buy_sol,
+            received_sol: row.received_sol,
+            buy_value_usd: row.buy_value_usd,
+            sell_value_usd: row.sell_value_usd,
+            profit_usd: row.profit_usd,
+            profit_percent: row.profit_percent,
+            buy_market_cap: row.buy_market_cap,
+            sell_market_cap: row.sell_market_cap,
+            market_cap_change: row.market_cap_change,
+            buy_liquidity: row.buy_liquidity,
+            sell_liquidity: row.sell_liquidity,
+            liquidity_change: row.liquidity_change,
+            last_updated: row.last_updated,
+            rapidDump: row.rapidDump,
+        };
+    }
+
     /**
      * Close the database connection gracefully.
      */
diff --git a/core/src/providers/trustScoreProvider.ts b/core/src/providers/trustScoreProvider.ts
index 1932d9ba587..f941111ac29 100644
--- a/core/src/providers/trustScoreProvider.ts
+++ b/core/src/providers/trustScoreProvider.ts
@@ -363,8 +363,7 @@ export class TrustScoreProvider {
         recommenderId: string,
         sellTimeStamp: string,
         sellDetails: sellDetails,
-        isSimulation: boolean,
-        buyTimeStamp: string
+        isSimulation: boolean
     ) {
         const processedData: ProcessedTokenData =
             await this.tokenProvider.getProcessedTokenData();
@@ -377,12 +376,12 @@ export class TrustScoreProvider {
         const sellSol = sellDetails.sell_amount / parseFloat(solPrice);
         const sell_value_usd =
             sellDetails.sell_amount * processedData.tradeData.price;
-        const trade = await this.trustScoreDb.getTradePerformance(
+        const trade = await this.trustScoreDb.getLatestTradePerformance(
             tokenAddress,
             recommenderId,
-            buyTimeStamp,
             isSimulation
         );
+        const buyTimeStamp = trade.buy_timeStamp;
         const marketCap =
             processedData.dexScreenerData.pairs[0]?.marketCap || 0;
         const liquidity =