
Security News
Attackers Are Hunting High-Impact Node.js Maintainers in a Coordinated Social Engineering Campaign
Multiple high-impact npm maintainers confirm they have been targeted in the same social engineering campaign that compromised Axios.
plugin-virtuals-acp
Advanced tools
This plugin integrates Virtuals Protocol's Agent Communication Protocol (ACP) into ElizaOS, enabling your AI agents to send and receive jobs through ACP.
onNewTaskRuntime → AcpService → AcpClient → Virtuals Protocol
↓
Custom Handlers
↓
Eliza Runtime
Flow Diagram (Updated with Runtime Integration):
Client ACP Service Handler Eliza Runtime
| | | |
|--query------->| | |
| (REQUEST) | | |
| |--Check---------->| |
| | Capability | |
| |<--Can Handle-----| |
| | | |
|<--Accept------| | |
| & Require | | |
| | | |
|--Payment----->| | |
| (TRANSACTION) | | |
| |--Process-------->| |
| | |--Create Memory------->|
| | |--Emit Event---------->|
| | | MESSAGE_RECEIVED |
| | | |
| | | [Process]
| | | |
| | |<--Callback(response)--|
| | | |
| | |--Deliver Result------>|
|<--Response----|<--ACP Deliver----| |
| | |<--Store Memory--------|
| | | |
Key Steps:
Use this minimal setup to initialize ACP and register job handlers after runtime is ready.
import { type IAgentRuntime, type ProjectAgent, logger } from "@elizaos/core";
import pluginVirtualsAcp from "./src/plugins/plugin-virtuals-acp";
import { defaultCharacter } from "./character";
/**
* Initialize ACP service with job type handlers
* MUST be called after runtime initialization
*/
export async function initializeAcpHandlers(
runtime: IAgentRuntime,
maxRetries: number = 5,
retryDelay: number = 1000
) {
for (let attempt = 1; attempt <= maxRetries; attempt++) {
const acpService = runtime.getService("virtuals-acp");
if (!acpService) {
if (attempt === maxRetries) {
logger.warn("⚠️ ACP service not found after all retries. Make sure ACP environment variables are set.");
return;
}
logger.info(`⏳ ACP service not ready yet, retrying (${attempt}/${maxRetries})...`);
await new Promise(resolve => setTimeout(resolve, retryDelay));
continue;
}
if (!("updateJobTypeRegistry" in acpService)) {
logger.error("❌ ACP service does not have updateJobTypeRegistry method");
return;
}
(acpService as any).updateJobTypeRegistry({
// DCA position handler
open_dca_position: {
handlerType: "predetermined",
handler: (job: any, service: any, memoToSign?: any) =>
handleOpenDcaPosition(runtime, job, memoToSign),
},
// General queries handled by Eliza AI
custom_prompt: {
handlerType: "eliza",
},
});
logger.success("✅ ACP handlers initialized: open_dca_position, custom_prompt");
return;
}
}
const initAcp = async ({ runtime }: { runtime: IAgentRuntime }) => {
logger.info("Initializing character: ", runtime.character.name);
setTimeout(async () => {
await initializeAcpHandlers(runtime);
}, 2000);
};
export const vaulterAcpConfig: ProjectAgent = {
character: defaultCharacter,
init: async (runtime: IAgentRuntime) => await initAcp({ runtime }),
plugins: [pluginVirtualsAcp],
};
The plugin uses @virtuals-protocol/acp-node which should already be installed:
bun add @virtuals-protocol/acp-node
Add these to your .env file:
# Required
ACP_WALLET_PRIVATE_KEY=0x... # Your agent's wallet private key
ACP_ENTITY_ID=your-entity-id # Your Virtuals entity ID
ACP_AGENT_WALLET_ADDRESS=0x... # Your agent's wallet address
# Optional
ACP_CONFIG_VERSION=V2 # ACP config version (default: V2)
For how wallets map to these variables and how to whitelist your dev wallet, see: Initialize and Whitelist Wallet.
In your character config, include the plugin. For full setup and handler registration, see Quickstart above.
Use the ACP dashboard to define the jobs your agent offers and their requirements.

For detailed steps, see the onboarding guide: ACP Onboarding Guide – Add the jobs you offer.
The plugin supports two routing strategies:
import virtualsAcpPlugin, { AcpService } from "./src/plugins/plugin-virtuals-acp";
import { AcpJob } from "@virtuals-protocol/acp-node";
// Define a predetermined handler
const handleDcaPosition = async (job: AcpJob, service: AcpService) => {
console.log("Handling DCA position:", job.id);
// Your custom logic here
await job.accept("Position accepted");
await service.createNotification(job.id, "Position opened successfully");
};
// Configure the plugin with job type registry
const acpPlugin = {
...virtualsAcpPlugin,
services: [
{
service: AcpService,
config: {
jobTypeRegistry: {
// Route to predetermined handler
"open_dca_position": {
handlerType: "predetermined",
handler: handleDcaPosition,
},
// Route to Eliza AI
"custom_prompt": {
handlerType: "eliza",
},
},
},
},
],
};
Get the ACP service from runtime:
const acpService = runtime.getService("virtuals-acp") as AcpService;
// Get active jobs
const activeJobs = await acpService.getActiveJobs(page, pageSize);
// Get completed jobs
const completedJobs = await acpService.getCompletedJobs(page, pageSize);
// Get cancelled jobs
const cancelledJobs = await acpService.getCancelledJobs(page, pageSize);
// Get jobs with pending memos
const pendingJobs = await acpService.getPendingMemoJobs(page, pageSize);
// Get specific job by ID
const job = await acpService.getJobById(jobId);
// Send a message to a job
await acpService.sendMessage(jobId, "Processing your request...", nextPhase);
// Create a notification
await acpService.createNotification(jobId, "Task completed successfully!");
// Create a memo
await acpService.createMemo(jobId, "Memo content", nextPhase);
// Get a memo by ID
const memo = await acpService.getMemoById(jobId, memoId);
// Get account by job ID
const account = await acpService.getAccountByJobId(jobId);
// Get account by client and provider addresses
const account = await acpService.getByClientAndProvider(clientAddress, providerAddress);
Here's a complete example of handling DCA position jobs:
import { AcpJob, AcpJobPhases } from "@virtuals-protocol/acp-node";
import { AcpService } from "./src/plugins/plugin-virtuals-acp";
interface DcaPosition {
positionId: string;
dailyAmount: number;
amount: number;
strategy: "conservative" | "moderate" | "aggressive";
}
const positions = new Map<string, DcaPosition[]>();
async function handleOpenPosition(job: AcpJob, service: AcpService) {
if (job.phase === AcpJobPhases.REQUEST) {
// Accept the request
await job.accept("Accepting position opening request");
// Request payment
await job.createPayableRequirement(
"Send USDC to open position",
MemoType.PAYABLE_REQUEST,
new FareAmount(payload.amount, config.baseFare),
job.providerAddress
);
} else if (job.phase === AcpJobPhases.TRANSACTION) {
// Open the position
const payload = job.requirement;
const positionId = `pos_${Date.now()}`;
// Store position
const clientPositions = positions.get(job.clientAddress) || [];
clientPositions.push({
positionId,
dailyAmount: payload.dailyAmount,
amount: payload.amount,
strategy: payload.strategy,
});
positions.set(job.clientAddress, clientPositions);
// Deliver confirmation
await job.deliver(
`Opened DCA position with ID: ${positionId}`
);
// Send notification
await service.createNotification(
job.id,
`Position ${positionId} opened successfully!`
);
}
}
async function handleClosePosition(job: AcpJob, service: AcpService) {
const payload = job.requirement;
const clientPositions = positions.get(job.clientAddress) || [];
const position = clientPositions.find(p => p.positionId === payload.positionId);
if (!position) {
await job.reject("Position not found");
return;
}
if (job.phase === AcpJobPhases.REQUEST) {
await job.accept("Closing position");
await job.createRequirement("Confirmed position closure");
} else if (job.phase === AcpJobPhases.TRANSACTION) {
// Return funds
await job.deliverPayable(
`Closed position ${position.positionId}`,
new FareAmount(position.amount, config.baseFare)
);
// Remove position
positions.set(
job.clientAddress,
clientPositions.filter(p => p.positionId !== payload.positionId)
);
}
}
// Configure the plugin
export const acpConfig = {
jobTypeRegistry: {
"open_dca_position": {
handlerType: "predetermined",
handler: handleOpenPosition,
},
"close_dca_position": {
handlerType: "predetermined",
handler: handleClosePosition,
},
},
};
FAQs
ElizaOS plugin for Virtuals ACP integration
We found that plugin-virtuals-acp demonstrated a healthy version release cadence and project activity because the last version was released less than a year ago. It has 1 open source maintainer collaborating on the project.
Did you know?

Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.

Security News
Multiple high-impact npm maintainers confirm they have been targeted in the same social engineering campaign that compromised Axios.

Security News
Axios compromise traced to social engineering, showing how attacks on maintainers can bypass controls and expose the broader software supply chain.

Security News
Node.js has paused its bug bounty program after funding ended, removing payouts for vulnerability reports but keeping its security process unchanged.