Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 11 additions & 8 deletions src/eliza/packages/plugin-solana/src/actions/airdrop.ts
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,17 @@ export const airdrop: Action = {
return true;
},
description: 'Perform claim airdrop for the user agent account',
formatParameters: async (runtime: IAgentRuntime, parameters: any, callback?: HandlerCallback) => {
const response = convertNullStrings(parameters);
if (!response.programName) {
const responseMsg = {
text: 'Please tell me the program name of the airdrop',
};
callback?.(responseMsg);
return {status: 'incomplete info', parameters: parameters};
}
return {status: 'success', parameters: parameters};
},
handler: async (
runtime: IAgentRuntime,
message: Memory,
Expand All @@ -59,14 +70,6 @@ export const airdrop: Action = {
}
const response = convertNullStrings(state.actionParameters);
elizaLogger.log('Response:', response);
if (!response.programName) {
const responseMsg = {
text: 'Please tell me the program name of the airdrop',
action: 'CLAIM_AIRDROP',
};
callback?.(responseMsg);
return 'pending';
}

const airdrops = await getAirdrops(runtime, message);
if (!airdrops) {
Expand Down
26 changes: 14 additions & 12 deletions src/eliza/packages/plugin-solana/src/actions/analyze.ts
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,20 @@ export const analyze: Action = {
},
description:
'Analyze the token trade info, twitter binding and news about the token by given symbol or contract address',
formatParameters: async (runtime: IAgentRuntime, parameters: any, callback?: HandlerCallback) => {
const formattedParameters = convertNullStrings(parameters) as any;
if (formattedParameters.tokenSymbol && !formattedParameters.tokenAddress) {
const tokens = await getTokensBySymbol(runtime, formattedParameters.tokenSymbol);
formattedParameters.tokenAddress = tokens?.[0]?.address;
}
if (!formattedParameters.tokenAddress) {
callback?.({
text: `Please provide either token symbol or contract address to analyze.`,
});
return {status: 'incomplete info', parameters: formattedParameters};
}
return {status: 'success', parameters: formattedParameters};
},
handler: async (
runtime: IAgentRuntime,
message: Memory,
Expand All @@ -59,18 +73,6 @@ export const analyze: Action = {
let response = convertNullStrings(state.actionParameters) as any;
elizaLogger.log('ANALYZE_TOKEN Response:', response);

if (response.tokenSymbol && !response.tokenAddress) {
const tokens = await getTokensBySymbol(runtime, response.tokenSymbol);
response.tokenAddress = tokens?.[0]?.address;
}

if (!response.tokenAddress) {
callback?.({
text: `Please provide either token symbol or contract address to analyze.`,
});
return 'pending';
}

const analyzeResult = await getTokenInfo(
response.tokenSymbol,
response.tokenAddress,
Expand Down
242 changes: 125 additions & 117 deletions src/eliza/packages/plugin-solana/src/actions/autoSwap.ts
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ export interface LimitOrderTask {
priceCondition: 'below' | 'above' | null;
targetPrice: number | null;
targetToken: string | null;
pendingConfirmation: boolean | null;
}


Expand Down Expand Up @@ -134,6 +135,92 @@ export const autoTask: Action = {
},
description:
'Perform auto token swap. Enables the agent to automatically execute trades when specified conditions are met, such as limit orders, scheduled transactions, or other custom triggers, optimizing trading strategies without manual intervention.',
formatParameters: async (runtime: IAgentRuntime, parameters: any, callback?: HandlerCallback) => {
elizaLogger.log('parameters (formatParameters): ', parameters);
const formattedParameters = parameters as LimitOrderTask;
if (formattedParameters.inputTokenSymbol?.toUpperCase() === 'SOL') {
formattedParameters.inputTokenCA = NATIVE_MINT.toBase58();
}
if (formattedParameters.outputTokenSymbol?.toUpperCase() === 'SOL') {
formattedParameters.outputTokenCA = NATIVE_MINT.toBase58();
}
formattedParameters.inputTokenCA = validateAndAssignCA(
formattedParameters.inputTokenSymbol,
formattedParameters.inputTokenCA,
);
formattedParameters.outputTokenCA = validateAndAssignCA(
formattedParameters.outputTokenSymbol,
formattedParameters.outputTokenCA,
);

formattedParameters.inputTokenCA = formattedParameters.inputTokenCA ||
formattedParameters.inputTokenSymbol? await getTokenCABySymbol(
runtime,
formattedParameters.inputTokenSymbol,
) : null;
formattedParameters.outputTokenCA = formattedParameters.outputTokenCA ||
formattedParameters.outputTokenSymbol? await getTokenCABySymbol(
runtime,
formattedParameters.outputTokenSymbol,
) : null;
formattedParameters.targetTokenCA =
(formattedParameters.targetToken === NATIVE_MINT.toBase58() ? NATIVE_MINT.toBase58() : null) ||
formattedParameters.targetTokenCA ||
(formattedParameters.targetToken === formattedParameters.inputTokenSymbol ? formattedParameters.inputTokenCA : null) ||
(formattedParameters.targetToken === formattedParameters.outputTokenSymbol ? formattedParameters.outputTokenCA : null) ||
formattedParameters.targetToken ? await getTokenCABySymbol(runtime, formattedParameters.targetToken) : null;

if (!formattedParameters.inputTokenCA || !isValidSPLTokenAddress(formattedParameters.inputTokenCA)) {
callback?.({
text: 'Please provide a valid inputToken CA you want to sell',
});
return {status: 'incomplete info', parameters: parameters};
}

if (!formattedParameters.outputTokenCA || !isValidSPLTokenAddress(formattedParameters.outputTokenCA)) {
callback?.({
text: 'Please provide a valid outputToken CA you want to buy',
});
return {status: 'incomplete info', parameters: parameters};
}

if (!formattedParameters.targetTokenCA || !isValidSPLTokenAddress(formattedParameters.targetTokenCA)) {
callback?.({
text: `Please specify which token's price you want to monitor: ${formattedParameters.inputTokenCA} or ${formattedParameters.outputTokenCA}?`,
});
return {status: 'incomplete info', parameters: parameters};
}

if (
Number.isFinite(formattedParameters.outputTokenAmount) &&
formattedParameters.outputTokenAmount != 0
) {
callback?.({
text: `Specify the buy amount of a token is not supported now, ${formattedParameters.outputTokenAmount} will be ignored.`,
});
return {status: 'incomplete info', parameters: parameters};
}

if (!formattedParameters.targetPrice && !formattedParameters.delay) {
callback?.({
text: "If you'd like to create an autotask, please specify the target price for the swap or provide a time delay, such as 'after 5 minutes' or 'below 0.00169' ",
});
return {status: 'incomplete info', parameters: parameters};
}

const client = await getSolanaClient(runtime);

if (
Number.isFinite(formattedParameters.inputTokenPercentage) &&
formattedParameters.inputTokenPercentage != 0
) {
const balance = await client.getUIBalance(formattedParameters.inputTokenCA);
formattedParameters.inputTokenAmount = balance * formattedParameters.inputTokenPercentage;
}


return {status: 'success', parameters: formattedParameters};
},
handler: async (
runtime: IAgentRuntime,
message: Memory,
Expand Down Expand Up @@ -193,94 +280,10 @@ async function checkResponse(
callback?.(NotAgentAdminResponse);
return {status: 'rejected'};
}

// generate formatted response from chat
let swapReq = convertNullStrings(state.actionParameters) as LimitOrderTask;
swapReq.inputTokenPercentage = Number(swapReq.inputTokenPercentage);
swapReq.inputTokenAmount = Number(swapReq.inputTokenAmount);

elizaLogger.log(`Response:`, swapReq);

if (swapReq.inputTokenSymbol?.toUpperCase() === 'SOL') {
swapReq.inputTokenCA = NATIVE_MINT.toBase58();
}
if (swapReq.outputTokenSymbol?.toUpperCase() === 'SOL') {
swapReq.outputTokenCA = NATIVE_MINT.toBase58();
}
swapReq.inputTokenCA = validateAndAssignCA(
swapReq.inputTokenSymbol,
swapReq.inputTokenCA,
);
swapReq.outputTokenCA = validateAndAssignCA(
swapReq.outputTokenSymbol,
swapReq.outputTokenCA,
);

swapReq.inputTokenCA = swapReq.inputTokenCA || await getTokenCABySymbol(
runtime,
swapReq.inputTokenSymbol,
);
swapReq.outputTokenCA = swapReq.outputTokenCA || await getTokenCABySymbol(
runtime,
swapReq.outputTokenSymbol,
);
swapReq.targetTokenCA = swapReq.targetTokenCA || await getTokenCABySymbol(
runtime,
swapReq.targetToken,
) || swapReq.targetToken === swapReq.inputTokenSymbol ? swapReq.inputTokenCA : swapReq.outputTokenCA;

if (!swapReq.inputTokenCA || !isValidSPLTokenAddress(swapReq.inputTokenCA)) {
callback?.({
text: 'Please provide a valid inputToken CA you want to sell',
});
return {status: 'pending'};
}

if (!swapReq.outputTokenCA || !isValidSPLTokenAddress(swapReq.outputTokenCA)) {
callback?.({
text: 'Please provide a valid outputToken CA you want to buy',
});
return {status: 'pending'};
}

if (!swapReq.targetTokenCA || !isValidSPLTokenAddress(swapReq.targetTokenCA)) {
callback?.({
text: `Please specify which token's price you want to monitor: ${swapReq.inputTokenCA} or ${swapReq.outputTokenCA}?`,
});
return {status: 'pending'};
}

if (
Number.isFinite(swapReq.outputTokenAmount) &&
swapReq.outputTokenAmount != 0
) {
callback?.({
text: `Specify the buy amount of a token is not supported now, ${swapReq.outputTokenAmount} will be ignored.`,
});
return {status: 'pending'};
}

const swapReq = state.actionParameters as LimitOrderTask;
elizaLogger.info(`swapReq: ${JSON.stringify(swapReq)}`);

const client = await getSolanaClient(runtime);

if (
Number.isFinite(swapReq.inputTokenPercentage) &&
swapReq.inputTokenPercentage != 0
) {
const balance = await client.getUIBalance(swapReq.inputTokenCA);
swapReq.inputTokenAmount = balance * swapReq.inputTokenPercentage;
}

if (
!Number.isFinite(swapReq.inputTokenAmount) ||
swapReq.inputTokenAmount <= 0
) {
callback?.({
text: `Please provide a valid ${swapReq.inputTokenSymbol} input amount to perform the swap`,
action: 'AUTO_TASK',
});
return {status: 'pending'};
}

const balance = await client.getUIBalance(swapReq.inputTokenCA);
if (!balance) {
callback?.({
Expand All @@ -289,7 +292,7 @@ async function checkResponse(
return {status: 'failed'};
}

if (balance < swapReq.inputTokenAmount) {
if (balance < Number(swapReq.inputTokenAmount)) {
callback?.({
text: `Insufficient balance for swap, required: ${swapReq.inputTokenAmount} but only ${balance} available.`,
});
Expand All @@ -311,7 +314,7 @@ async function checkResponse(
});
return {status: 'failed'};
}
} else if (WSOL_AMOUNT - swapReq.inputTokenAmount < GAS_BALANCE) {
} else if (WSOL_AMOUNT - Number(swapReq.inputTokenAmount) < GAS_BALANCE) {
// buy with SOL
const requiredAmount = GAS_BALANCE + Number(swapReq.inputTokenAmount);
elizaLogger.error('Insufficient balance for swap gas fee');
Expand All @@ -323,13 +326,6 @@ async function checkResponse(
return {status: 'failed'};
}

if (!swapReq.targetPrice && !swapReq.delay) {
callback?.({
text: "If you'd like to create an autotask, please specify the target price for the swap or provide a time delay, such as 'after 5 minutes' or 'below 0.00169' ",
});
return {status: 'pending'};
}

if (swapReq.delay) {
const getSecondsValue = (value: string): number | null => {
const match = value.match(/^(\d+)s$/);
Expand All @@ -345,28 +341,42 @@ async function checkResponse(

elizaLogger.info(`checking if user confirm to create task`);

const confirmContext = composeContext({
state,
template: userConfirmTemplate,
});

const confirmResponse = await generateObjectDeprecated({
runtime,
context: confirmContext,
modelClass: ModelClass.LARGE,
});
elizaLogger.info(`User confirm check: ${JSON.stringify(confirmResponse)}`);
if (swapReq.pendingConfirmation === true) {
const confirmContext = composeContext({
state,
template: userConfirmTemplate,
});

if (confirmResponse.userAcked == 'rejected') {
callback?.({
text: 'ok. I will not set the autotask.',
action: 'AUTO_TASK',
const confirmResponse = await generateObjectDeprecated({
runtime,
context: confirmContext,
modelClass: ModelClass.LARGE,
});
return {status: 'cancelled'};
}
elizaLogger.info(`User confirm check: ${JSON.stringify(confirmResponse)}`);

if (confirmResponse.userAcked == 'pending') {
swapReq.inputTokenPercentage = (swapReq.inputTokenAmount/balance);
if (confirmResponse.userAcked == 'rejected') {
callback?.({
text: 'ok. I will not set the autotask.',
action: 'AUTO_TASK',
});
return {status: 'cancelled'};
} else if (confirmResponse.userAcked == 'confirmed') {
return {status: 'success', task: swapReq};
} else if (confirmResponse.userAcked == "pending") {
callback?.({
text: "I repeatedly asked you to confirm the task although you have already confirmed it. It was my mistake. Please try again.",
action: "AUTO_TASK"
});
return { status: "pending" };
} else {
callback?.({
text: "I failed to recognize your confirmation. Please try again.",
action: "AUTO_TASK"
});
return { status: "failed" };
}
} else {
swapReq.inputTokenPercentage = Number(swapReq.inputTokenAmount)/balance;
const swapInfo = formatTaskInfo(swapReq);
callback?.({
text: `${swapInfo}`,
Expand All @@ -375,8 +385,6 @@ async function checkResponse(
});
return {status: 'pending'};
}

return {status: 'success', task: swapReq};
}


Expand Down
Loading