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
- Implementation: Create a Java class implementing the
VariableProvider
interface - Packaging: Bundle the compiled class and its dependencies into a JAR file
- Deployment: Copy the JAR to the
plugins/ext-jars
folder - Configuration: Add variable provider class name to the
application-plugins.yml
undervariable-providers
- Auto-loading: Yaci-store automatically discovers and loads all Variable Providers at startup
- Usage: Variables become available to all plugin scripts immediately
Benefits of Variable Providers
- No Core Modifications: Extend functionality without touching yaci-store source code
- Modular Design: Each provider is independent and can be added/removed easily
- Reusability: Share providers across different yaci-store instances
- 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
orapplication.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:
- Variable not found: Ensure the Variable Provider JAR is in
plugins/ext-jars
- ClassNotFoundException: Check that all dependencies are included in the JAR
- 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
orkafka
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.