This page provides practical examples of how to use the AVA SDK JS to interact with the Ava Protocol's Actively Validated Services (AVS).
This page provides practical examples of how to use the AVA SDK JS to interact with the Ava Protocol's Actively Validated Services (AVS). It demonstrates common use cases including authentication, workflow creation, workflow management, and secret management. For details about the SDK architecture, refer to SDK Architecture.
Before interacting with the AVS service, you must authenticate using either a signature or an API key.
async function generateSignature(privateKey) {
const wallet = new ethers.Wallet(privateKey);
const keyRequestParams = {
chainId: 11155111,
address: wallet.address,
issuedAt: new Date(),
expiredAt: new Date(new Date().getTime() + 3600),
};
const message = getKeyRequestMessage(keyRequestParams);
const signature = await wallet.signMessage(message);
return { signature, ...keyRequestParams };
}
async function authenticate() {
const signature = await generateSignature(privateKey);
const result = await client.authWithSignature(signature);
client.setAuthKey(result.authKey);
return result;
}
Sources:
Sources:
// Creates a new smart wallet with provided salt and factory address
const createWallet = async (owner, token, salt, factoryAddress) => {
return await client.getWallet(
{
salt,
factoryAddress,
},
{
authKey: token,
}
);
};
Sources:
// Get all wallets associated with the authenticated user
async function getWallets(owner, token, shouldFetchBalances) {
const walletsResp = await client.getWallets({
authKey: token,
});
// Optionally fetch wallet balances
if (shouldFetchBalances) {
// Fetch and return wallets with balance information
// ...
}
return walletsResp;
}
Sources:
Workflows are the core component of the AVA SDK and consist of a trigger, nodes, and edges.
A workflow consists of:
Sources:
// Create a workflow
const workflow = client.createWorkflow({
name: `workflow name`,
smartWalletAddress, // The smart wallet that owns this workflow
nodes: NodeFactory.createNodes([
// Define nodes here
]),
edges: [
// Define connections between nodes
],
trigger: TriggerFactory.create({
// Define trigger type and configuration
}),
startAt: Math.floor(Date.now() / 1000) + 30, // Start 30 seconds from now
expiredAt: Math.floor(Date.now() / 1000 + 3600 * 24 * 30), // Expire in 30 days
maxExecution: 0, // 0 means unlimited executions
});
// Submit the workflow to the AVS service
const workflowId = await client.submitWorkflow(workflow, {
authKey,
});
Sources:
Executes a workflow every N blocks.
const trigger = TriggerFactory.create({
id: triggerId,
type: TriggerType.Block,
name: "blockTrigger",
data: {
interval: 5, // Execute every 5 blocks
},
});
Sources:
Executes a workflow based on a cron schedule.
const trigger = TriggerFactory.create({
id: triggerId,
type: TriggerType.Cron,
name: "cronTrigger",
data: {
scheduleList: ["*/2 * * * *"], // Every 2 minutes
},
});
Sources:
Executes a workflow when a specific event (like a contract event) occurs.
const trigger = TriggerFactory.create({
id: triggerId,
type: TriggerType.Event,
name: "eventTrigger",
data: {
expression: "",
matcherList: [
{
type: "topics",
valueList: [
"0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef", // Transfer event signature
"",
targetAddress, // Address to monitor
],
},
],
},
});
Sources:
Sources:
Used to read data from a smart contract.
{
id: nodeId,
name: "checkPrice",
type: NodeType.ContractRead,
data: {
contractAddress: oraclePriceContract,
callData: "0xfeaf968c", // Function selector
contractAbi: `[...]`, // Contract ABI
},
}
Sources:
Used to interact with external REST APIs.
{
id: nodeId,
name: "notification",
type: NodeType.RestAPI,
data: {
url: "https://api.example.com/endpoint",
method: "POST",
body: `{ "message": "Dynamic content: {{ previousNode.data.toString() }}" }`,
headersMap: [["content-type", "application/json"]],
},
}
Sources:
Used for conditional logic within workflows.
{
id: nodeId,
name: "checkAmount",
type: NodeType.Branch,
data: {
conditionsList: [
{
id: branchConditionId,
type: "if",
expression: `someValue > 1`, // JavaScript expression
},
],
},
}
Sources:
This example creates a workflow that periodically checks an Oracle price feed and sends notifications.
async function schedulePriceReport(schedule, smartWalletAddress, { authKey }) {
// Create price monitoring workflow
const workflow = client.createWorkflow({
name: `price report every 5 blocks`,
smartWalletAddress,
nodes: NodeFactory.createNodes([
// Node to read price from Oracle contract
{
id: nodeIdOraclePrice,
name: "checkPrice",
type: NodeType.ContractRead,
data: {
contractAddress: oraclePriceContract,
callData: "0xfeaf968c",
contractAbi: `[...]`,
},
},
// Node to send notification with price data
{
id: nodeIdNotification,
name: "notification",
type: NodeType.RestAPI,
data: {
url: "https://webhook.example.com",
method: "POST",
body: `{ "text": "The price is {{ checkPrice.data.toString() }}." }`,
headersMap: [["content-type", "application/json"]],
},
},
]),
edges: [
// Connect trigger to price check
new Edge({
id: UlidMonotonic.generate().toCanonical(),
source: triggerId,
target: nodeIdOraclePrice,
}),
// Connect price check to notification
new Edge({
id: UlidMonotonic.generate().toCanonical(),
source: nodeIdOraclePrice,
target: nodeIdNotification,
}),
],
trigger: TriggerFactory.create({...}), // Block or Cron trigger
startAt: Math.floor(Date.now() / 1000) + 30,
expiredAt: Math.floor(Date.now() / 1000 + 3600 * 24 * 30),
maxExecution: 0,
});
const workflowId = await client.submitWorkflow(workflow, { authKey });
return workflowId;
}
Sources:
This example creates a workflow that monitors token transfers to a specific address.
async function scheduleMonitorTransfer(owner, token, target) {
// Get the first available smart wallet
const wallets = await getWallets(owner, token);
const smartWalletAddress = wallets[0].address;
// Create monitoring workflow
const workflow = client.createWorkflow({
name: `monitor transfer`,
smartWalletAddress,
nodes: NodeFactory.createNodes([
// Check if transfer amount exceeds threshold
{
id: nodeIdCheckAmount,
name: "checkAmount",
type: NodeType.Branch,
data: {
conditionsList: [
{
id: branchIdCheckAmount,
type: "if",
expression: `demoTriggerName.data.address == "0x036cbd53842c5426634e7929541ec2318f3dcf7e" && Number(demoTriggerName.data.value_formatted) > 0.005`,
},
],
},
},
// Send notification about the transfer
{
id: nodeIdNotification,
name: "notification",
type: NodeType.RestAPI,
data: {
url: "https://webhook.example.com",
method: "POST",
body: `{ "text": "Received {{demoTriggerName.data.value_formatted}} tokens" }`,
headersMap: [["content-type", "application/json"]],
},
},
]),
edges: [...], // Connect nodes
trigger: TriggerFactory.create({
id: triggerId,
type: TriggerType.Event,
name: "demoTriggerName",
data: {
expression: "",
matcherList: [
{
type: "topics",
valueList: [
"0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef", // Transfer event topic
"",
target, // Address to monitor
],
},
],
},
}),
startAt: Math.floor(Date.now() / 1000) + 30,
expiredAt: Math.floor(Date.now() / 1000 + 3600 * 24 * 30),
maxExecution: 0,
});
const workflowId = await client.submitWorkflow(workflow, { authKey: token });
return workflowId;
}
Sources:
This example creates a workflow that automatically sweeps tokens from a smart wallet to another address when tokens are received.
async function scheduleSweep(owner, token, target) {
// Get the first available smart wallet
const wallets = await getWallets(owner, token);
const smartWalletAddress = wallets[0].address;
// Create auto-sweep workflow
const workflow = client.createWorkflow({
name: `auto-sweep`,
smartWalletAddress,
nodes: NodeFactory.createNodes([
// Check if token is on whitelist
{
id: nodeIdCheckToken,
name: "checktoken",
type: NodeType.Branch,
data: {
conditionsList: [
{
id: branchIdCheckToken,
type: "if",
expression: `["0x036cbd53842c5426634e7929541ec2318f3dcf7e", "0xe4ab69c077896252fafbd49efd26b5d171a32410"].indexOf(demoTriggerName.data.address.toLowerCase()) >= 0`,
},
],
},
},
// Log the transfer
{
id: nodeIdLog,
name: "log",
type: NodeType.RestAPI,
data: {...},
},
// Transfer tokens to destination address
{
id: nodeIdSweep,
name: "sweep",
type: NodeType.ContractWrite,
data: {
contractAddress: "{{demoTriggerName.data.address}}", // Dynamic token address
callData: "0xa9059cbb000000000000000000000000e0f7d11fd714674722d325cd86062a5f1882e13a{{ Number(demoTriggerName.data.value).toString(16).padStart(64, '0') }}", // Transfer function call
contractAbi: "[]",
},
},
]),
edges: [...], // Connect nodes
trigger: TriggerFactory.create({
id: triggerId,
type: TriggerType.Event,
name: "demoTriggerName",
data: {
expression: "",
matcherList: [
{
type: "topics",
valueList: [
"0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef", // Transfer event topic
"",
target, // Address to monitor
],
},
],
},
}),
startAt: Math.floor(Date.now() / 1000) + 30,
expiredAt: Math.floor(Date.now() / 1000 + 3600 * 24 * 30),
maxExecution: 0,
});
const workflowId = await client.submitWorkflow(workflow, { authKey: token });
return workflowId;
}
Sources:
// Get workflows for one or more smart wallet addresses
async function getWorkflows(addresses, options) {
const result = await client.getWorkflows(addresses, {
authKey: options.authKey,
cursor: options.cursor,
limit: options.limit,
});
return result;
}
// Get details for a specific workflow
async function getWorkflow(workflowId, authKey) {
const result = await client.getWorkflow(workflowId, {
authKey,
});
return result;
}
Sources:
// Cancel a workflow (prevent future executions)
async function cancelWorkflow(workflowId, authKey) {
const result = await client.cancelWorkflow(workflowId, {
authKey,
});
return result;
}
// Delete a workflow completely
async function deleteWorkflow(workflowId, authKey) {
const result = await client.deleteWorkflow(workflowId, {
authKey,
});
return result;
}
Sources:
// Get executions for one or more workflows
async function getExecutions(workflowIds, options) {
const result = await client.getExecutions(workflowIds, {
authKey: options.authKey,
cursor: options.cursor,
limit: options.limit,
});
return result;
}
// Get details for a specific execution
async function getExecution(workflowId, executionId, authKey) {
const result = await client.getExecution(workflowId, executionId, {
authKey,
});
return result;
}
Sources:
// Manually trigger a workflow
async function triggerWorkflow(workflowId, triggerReason, authKey) {
const metadata = JSON.parse(triggerReason);
const result = await client.triggerWorkflow(
{
id: workflowId,
reason: {
type: TriggerType.Event,
blockNumber: metadata["block_number"],
logIndex: metadata["log_index"],
txHash: metadata["tx_hash"],
},
isBlocking: true, // Wait for execution to complete
},
{
authKey,
}
);
return result;
}
Sources:
// Create a new secret
async function createSecret(name, value, authKey) {
const result = await client.createSecret(name, value, {
authKey,
});
return result;
}
Sources:
// List all secrets
async function listSecrets(authKey) {
const secrets = await client.listSecrets({
authKey,
});
return secrets;
}
The following diagram shows a complete workflow example that monitors token transfers and sends notifications: