Plugin Framework
Context Variables

Plugin Context Variables

Built-in Variables and Custom Variable Providers for Plugin Development

Overview

Context variables are pre-defined objects and utilities that are automatically available to all plugin scripts without any import or initialization. These variables provide access to essential functionality like database operations, HTTP requests, state management, and more.

The plugin framework also supports custom Variable Providers, allowing developers to extend the available variables by implementing their own providers and bundling them as JAR files.

Built-in Variables

Yaci-store provides several built-in variables through the DefaultVariableProvider. These variables are automatically injected into the plugin execution context and can be used directly in your scripts.

Core Variables

named_jdbc (Recommended)

Type: NamedParameterJdbcTemplate Purpose: Execute SQL queries with named parameters for better readability and safety.

MVEL Example:

// Query with named parameters (much more readable)
params = ["address": utxo.ownerAddr, "minAmount": 1000000];
result = named_jdbc.queryForMap("SELECT COUNT(*) as count FROM address_utxo WHERE owner_addr = :address AND lovelace_amount > :minAmount", params);
count = result["count"];

// Query for multiple records with named parameters
params = ["address": utxo.ownerAddr];
rows = named_jdbc.queryForList("SELECT tx_hash, lovelace_amount FROM address_utxo WHERE owner_addr = :address", params);
for (row: rows) {
  tx_hash = row['tx_hash'];
  amount = row['lovelace_amount'];
}

// Update with named parameters
updateParams = ["txHash": txHash, "newStatus": "confirmed", "timestamp": System.currentTimeMillis()];
named_jdbc.update("UPDATE tx_status SET status = :newStatus, updated_at = :timestamp WHERE tx_hash = :txHash", updateParams);

// Complex query with multiple parameters
queryParams = ["poolId": poolId, "fromEpoch": 350, "toEpoch": 360];
delegations = named_jdbc.queryForObject("SELECT COUNT(*) FROM delegation WHERE pool_id = :poolId AND epoch >= :fromEpoch AND epoch <= :toEpoch", queryParams);

jdbc

Type: JdbcTemplate Purpose: Direct database access using JDBC for executing SQL queries and updates.

MVEL Example:

// Query for a single value (count, sum, etc.)
result = jdbc.queryForMap("SELECT COUNT(*) as cnt FROM address_utxo WHERE owner_addr = ?", address);
count = result['cnt'];

// Query for multiple records
rows = jdbc.queryForList("SELECT tx_hash, lovelace_amount FROM address_utxo WHERE owner_addr = ?", address);
for (row: rows) {
  tx_hash = row['tx_hash'];
  amount = row['lovelace_amount'];
}

// Execute update/insert/delete
jdbc.update("UPDATE custom_table SET processed = true WHERE tx_hash = ?", txHash);
jdbc.update("INSERT INTO tx_log (tx_hash, status) VALUES (?, ?)", txHash, "processed");

JavaScript Example:

// Query for a single value (count, sum, etc.)
var result = jdbc.queryForMap("SELECT COUNT(*) as cnt FROM address_utxo WHERE owner_addr = ?", address);
console.log(result.get("cnt"));

http

Type: PluginHttpClient Purpose: Simplified HTTP client specifically designed for plugin developers with common operations.

MVEL Example:

// GET request
response = http.get("https://jsonplaceholder.typicode.com/todos/1", ["header1": "value1", "header2": "value2"]);
if (response.status == 200) {
    System.out.println("Response: " + response.body);
} else {
    System.out.println("Failed to fetch data: " + response.status);
}

// POST JSON
jsonData = ["title": "foo", "body": "bar", "userId": 1];
response = http.postJson("https://jsonplaceholder.typicode.com/posts", jsonData, ["Content-Type": "application/json"]);

// POST form data
formData = ["title": txHash, "status": "processed"];
response = http.postForm("https://api.example.com/notify", formData, null);

// PUT JSON
updateData = ["status": "confirmed"];
response = http.putJson("https://api.example.com/tx/" + txHash, updateData, ["Authorization": "Bearer token"]);

// DELETE request
response = http.delete("https://api.example.com/resource/" + id, ["Authorization": "Bearer token"]);

rest

Type: RestTemplate Purpose: Make HTTP/REST API calls to external services.

MVEL Example:

// GET request
response = rest.getForObject("https://api.example.com/data", java.lang.String);

// POST request with JSON (simple approach)
jsonData = '{"transaction": "' + txHash + '", "amount": ' + amount + '}';
response = rest.postForObject("https://api.example.com/webhook", jsonData, java.lang.String);

// Note: For more complex HTTP operations, consider using the 'http' variable instead

env

Type: Environment Purpose: Access environment variables and application configuration properties.

MVEL Example:

// Get configuration with default value
webhookUrl = env.getProperty("plugin.webhook.url", "http://localhost:8080/webhook");
dbTimeout = env.getProperty("plugin.db.timeout", "30");

// Check if property exists
if (env.containsProperty("plugin.feature.enabled")) {
    enabled = env.getProperty("plugin.feature.enabled", Boolean.class);
}

state

Type: State Purpose: Plugin-specific persistent state management that survives between executions.

MVEL Example:

// Store and retrieve values
state.put("last_processed_slot", currentSlot);
state.put("transaction_count", 1000);

// Retrieve values
lastSlot = state.get("last_processed_slot");
count = state.get("transaction_count");

// Check if key exists
if (state.containsKey("last_processed_slot")) {
    // Process based on stored state
}

global_state

Type: State Purpose: Global state shared across all plugins for cross-plugin communication and data sharing.

⚠️

Note: Due to a bug in version 2.0.0-beta3, global state must be accessed using global_state.global() in plugins instead of directly using global_state.

MVEL Example:

// Store global data (accessible by all plugins)
global_state.global().put("network_tip", currentSlot);
global_state.global().put("epoch_info", epochData);

// Retrieve global data in any plugin
networkTip = global_state.global().get("network_tip");
epochInfo = global_state.global().get("epoch_info");

locker

Type: Locker Purpose: Thread-safe locking mechanism for synchronizing access to shared resources.

MVEL Example:

//TODO

All built-in variables are directly accessible by name in your plugin scripts. For example, you can use jdbc.queryForObject(...) directly without any initialization.

Variable Providers

Variable Providers are a powerful extension mechanism that allows developers to add new variables to the plugin context without modifying the core yaci-store codebase.

What is a Variable Provider?

A Variable Provider is a Java class that implements the VariableProvider interface. It defines:

  • A map of variable names to their corresponding objects
  • The lifecycle and initialization of these variables

How Variable Providers Work

  1. Implementation: Create a Java class implementing the VariableProvider interface
  2. Packaging: Bundle the compiled class and its dependencies into a JAR file
  3. Deployment: Copy the JAR to the plugins/ext-jars folder
  4. Configuration: Add variable provider class name to the application-plugins.yml under variable-providers
  5. Auto-loading: Yaci-store automatically discovers and loads all Variable Providers at startup
  6. Usage: Variables become available to all plugin scripts immediately

Benefits of Variable Providers

  1. No Core Modifications: Extend functionality without touching yaci-store source code
  2. Modular Design: Each provider is independent and can be added/removed easily
  3. Reusability: Share providers across different yaci-store instances
  4. Community Ecosystem: Share and use community-developed providers
⚠️

Variable Providers must be thread-safe as they may be accessed concurrently by multiple plugins executing in parallel.

Best Practices

For Variable Provider Developers

  • Use descriptive variable names that don't conflict with built-ins
  • Implement proper error handling and logging
  • Document required configuration and dependencies

Configuration

Variable Providers may require configuration through:

  • Application properties (application.yml or application.properties)
  • Environment variables
  • Configuration files in the config directory
  • Provider-specific configuration mechanisms

Some Variable Providers may require API keys, connection strings, or other configuration. Always check the provider's documentation for setup requirements.

Troubleshooting

Common issues with context variables:

  1. Variable not found: Ensure the Variable Provider JAR is in plugins/ext-jars
  2. ClassNotFoundException: Check that all dependencies are included in the JAR
  3. Configuration errors: Verify required configuration properties are set

Use Cases and Ideas for Custom Variable Providers

Custom Variable Providers enable powerful integrations and functionality extensions:

Communication Services

  • Telegram Bot Integration: Expose a telegram variable for sending notifications
  • Slack Webhooks: Provide slack variable for team alerts
  • Email Service: Add email variable for sending automated emails
  • SMS Gateway: Include sms variable for critical alerts

External Integrations

  • IPFS Client: Expose ipfs variable for decentralized storage operations
  • Redis Client: Provide redis variable for caching and pub/sub
  • Elasticsearch: Add elastic variable for advanced search capabilities
  • Message Queues: Include rabbitmq or kafka variables for event streaming

Blockchain Integrations

  • Blockfrost API: Expose blockfrost variable for additional blockchain queries
  • Koios API: Provide koios variable as an alternative data source

Monitoring and Analytics

  • Prometheus Metrics: Expose metrics variable for custom metric collection
  • Custom Loggers: Provide specialized logging capabilities
  • Analytics Services: Add variables for tracking and analytics platforms

The context variable system, combined with custom Variable Providers, makes yaci-store's plugin framework extremely flexible and extensible, allowing for sophisticated integrations without modifying the core system.