hookah-sdk
TypeScript SDK for interacting with Hookah's webhook and event monitoring system for Radix DLT.
Installation
npm install hookah-sdk
yarn add hookah-sdk
pnpm add hookah-sdk
Quick Start
import { createHookahSdk } from 'hookah-sdk';
const keypair = new PrivateKey.Ed25519(PRIVATE_KEY);
const sdk = createHookahSdk({
publicKey: keypair.publicKey(),
personaLabel: 'ED',
signer: async (message) => keypair.signToSignature(message).hex(),
});
// Authenticate
await sdk.auth();
// Get typed tRPC client for API interactions
const client = await sdk.getTrpcClient();
// Use the client to manage webhooks
await client.webhook.create.mutate({
url: 'https://your-webhook-endpoint.com',
});
Usage Examples
Initialization & Authentication
First, import the necessary function and initialize the SDK. You'll need a Radix compatible keypair for signing authentication challenges.
import { createHookahSdk } from 'hookah-sdk';
import { createEd25519KeyPair } from 'radix-web3.js';
// Replace with your actual private key
const privateKeyHex = 'YOUR_ED25519_PRIVATE_KEY_HEX';
const keypair = createEd25519KeyPair(privateKeyHex);
const sdk = createHookahSdk({
publicKey: keypair.publicKey(),
personaLabel: 'ED', // A label for your application/persona
signer: async (message) => keypair.signToSignature(message).hex(),
});
// Authenticate to get a session
try {
await sdk.auth();
console.log('Authentication successful!');
// Get the type-safe tRPC client for further interactions
const client = await sdk.getTrpcClient();
// Verify session (optional)
const { session } = await client.auth.getSession.query();
console.log('User ID:', session.userId);
} catch (error) {
console.error('Authentication failed:', error);
}
Webhook Management
Once authenticated, you can manage webhooks using the tRPC client.
// Assuming 'client' is the authenticated tRPC client from the previous step
const webhookUrl = 'https://your-server.com/webhook-handler';
const webhookName = 'My Transaction Monitor';
try {
// Create a new webhook
const createdWebhook = await client.webhook.create.mutate({
url: webhookUrl,
name: webhookName,
// Optional: Add custom headers
// headerKey: 'X-API-Key',
// headerValue: 'your-secret-key'
});
const webhookId = createdWebhook.id;
console.log('Webhook created with ID:', webhookId);
// Get all webhooks for the user
const webhooks = await client.webhook.getByUserId.query();
console.log('User webhooks:', webhooks);
// Get a specific webhook by ID
const specificWebhook = await client.webhook.getById.query({ id: webhookId });
console.log('Fetched webhook:', specificWebhook);
// Delete the webhook (uncomment to run)
/*
await client.webhook.remove.mutate({ id: webhookId });
console.log('Webhook deleted.');
const remainingWebhooks = await client.webhook.getByUserId.query();
console.log('Remaining webhooks:', remainingWebhooks);
*/
} catch (error) {
console.error('Webhook management error:', error);
}
Trigger Management
Triggers define the conditions under which a webhook fires.
// Assuming 'client' and 'webhookId' are available
// Example: Trigger on a specific event from a specific component address
const emitterAddress = 'component_tdx_a_1q...'; // Replace with actual address
const eventName = 'DepositEvent'; // Or any other event name
try {
// Create a simple trigger
const simpleTrigger = await client.trigger.create.mutate({
webhookId: webhookId,
emitterAddress: emitterAddress,
eventName: eventName,
status: 'active', // 'active' or 'inactive'
});
const simpleTriggerId = simpleTrigger.id;
console.log('Simple trigger created:', simpleTriggerId);
// Create a trigger with conditions
const conditionalTrigger = await client.trigger.create.mutate({
webhookId: webhookId,
emitterAddress: 'account_tdx_b_1q...', // Replace with account address
eventName: 'DepositEvent',
status: 'active',
conditions: [
{
id: 'cond1', // Unique ID for the condition within the trigger
field: 'amount', // Field from the event data to check
comparator: 'gte_n', // Comparator (e.g., 'eq_n', 'gte_n', 'lt_s', 'contains_s')
value: '1000', // Value to compare against (as string)
},
{
id: 'cond2',
field: 'resourceAddress', // Check the resource address
comparator: 'eq_s',
value: 'resource_tdx_c_1q...', // Replace with XRD or other resource address
},
],
});
console.log('Conditional trigger created:', conditionalTrigger.id);
// Get all triggers for the user
const triggers = await client.trigger.getByUserId.query();
console.log('User triggers:', triggers);
// Delete triggers (uncomment to run)
/*
for (const trigger of triggers) {
await client.trigger.remove.mutate({ id: trigger.id });
console.log('Deleted trigger:', trigger.id);
}
*/
} catch (error) {
console.error('Trigger management error:', error);
}
Viewing Logs
You can query the execution logs for your webhooks.
// Assuming 'client' and 'webhookId' are available
try {
// Get all logs for a specific webhook
const logs = await client.log.getByWebhookId.query({ webhookId: webhookId });
console.log(`Logs for webhook ${webhookId}:`);
logs.forEach((log) => {
console.log(
`- ID: ${log.id}, Status: ${log.status}, TxID: ${log.transactionId}, Time: ${log.executedAt}`,
);
// Log data contains the event payload sent to the webhook
// console.log(' Data:', log.data);
});
// Get all logs for the user
const allUserLogs = await client.log.getByUserId.query();
console.log('All user logs:', allUserLogs.length);
} catch (error) {
console.error('Error fetching logs:', error);
}
Trigger Conditions
Trigger conditions allow you to filter events based on the specific data they contain. When you create a trigger, you can optionally add an array of conditions. The webhook will only fire if all conditions associated with the trigger evaluate to true for a given event.
Each condition object has the following structure:
id
: A unique identifier for the condition within the trigger (e.g.,'cond1'
).field
: A string specifying the path to the data field within the event payload you want to check.comparator
: A string defining the comparison operation to perform. The available comparators depend on the expected data type of the field:- Numbers (
_n
):'eq_n'
: Equal to'neq_n'
: Not equal to'gt_n'
: Greater than'lt_n'
: Less than'gte_n'
: Greater than or equal to'lte_n'
: Less than or equal to
- Strings (
_s
):'eq_s'
: Equal to'neq_s'
: Not equal to'c_s'
: Contains'nc_s'
: Does not contain'sw_s'
: Starts with'ew_s'
: Ends with
- Booleans (
_b
):'eq_b'
: Equal to (true/false)'neq_b'
: Not equal to (true/false)
- Numbers (
value
: A string representing the value to compare the field against. Important: Even for numeric or boolean comparisons, provide the value as a string (e.g.,'1000'
,'true'
). The system will handle the type conversion based on the comparator used.
See the examples below for practical usage.
Event data tools:
- sbor-ez-mode-ez-mode
- Create a zod like schema from event data
- sbor-ez-mode-mcp-server
- also available as a MCP server
Trigger conditions schemas:
Conditions are an array of objects that define the conditions for the trigger.
const conditionalTrigger = await client.trigger.create.mutate({
webhookId,
emitterAddress: 'account_tdx_b_1q...', // Replace with account address
eventName: 'DepositEvent',
status: 'active',
conditions: [TRIGGER_CONDITIONS],
});
Trigger conditions examples
// Example 1: Trigger on a specific event from a specific component address
[{
id: 'cond1',
field: 'amount',
comparator: 'gte_n',
value: '1000',
}]
// Example 2: String value is equal to 'XRD'
[{
id: 'cond2',
field: 'x_token_address',
comparator: 'eq_s',
value: XRD_TOKEN_ADDRESS,
},]
// Example 3: String value contains 'XRD'
[{
id: 'cond3',
field: 'x_token_address',
comparator: 'contains_s',
value: 'XRD',
}]
// Example 4: String value starts with 'XRD'
[{
id: 'cond4',
field: 'x_token_address',
comparator: 'sw_s',
value: 'XRD',
},]
// Example 5: String value ends with 'XRD'
[{
id: 'cond5',
field: 'x_token_address',
comparator: 'ew_s',
value: 'XRD',
}]
Zod schemas for trigger conditions
import { z } from 'zod';
export const TriggerRuleV1Schema = z.object({
emitterAddress: z.string(),
eventName: z.string(),
});
export const TriggerDefinitionV1Schema = z.object({
version: z.literal(1),
rules: z.array(TriggerRuleV1Schema).length(1),
});
export const TriggerV1Schema = TriggerDefinitionV1Schema.extend({
id: z.string(),
});
export type TriggerV1 = z.infer<typeof TriggerV1Schema>;
export type TriggerRuleV1 = z.infer<typeof TriggerRuleV1Schema>;
export type TriggerDefinitionV1 = z.infer<typeof TriggerDefinitionV1Schema>;
const numberOperators = z.union([
z.literal('eq_n'), // equals
z.literal('neq_n'), // not equals
z.literal('gt_n'), // greater than
z.literal('lt_n'), // less than
z.literal('gte_n'), // greater than or equal to
z.literal('lte_n'), // less than or equal to
]);
const stringOperators = z.union([
z.literal('eq_s'), // equals
z.literal('neq_s'), // not equals
z.literal('c_s'), // contains
z.literal('nc_s'), // not contains
z.literal('sw_s'), // starts with
z.literal('ew_s'), // ends with
]);
const booleanOperators = z.union([
z.literal('eq_b'), // equals
z.literal('neq_b'), // not equals
]);
export const ComparatorSchema = z.union([
numberOperators,
stringOperators,
booleanOperators,
]);
export const TriggerConditionSchema = z.object({
id: z.string(),
field: z.string(),
comparator: ComparatorSchema,
value: z.string(),
});
export const TriggerRuleV2Schema = z.object({
emitterAddress: z.string(),
eventName: z.string(),
conditions: z.array(TriggerConditionSchema),
});
export const TriggerDefinitionV2Schema = z.object({
version: z.literal(2),
rules: z.array(TriggerRuleV2Schema).length(1),
});
export const TriggerDefinitionSchema = z.union([
TriggerDefinitionV1Schema,
TriggerDefinitionV2Schema,
]);
export type TriggerDefinition = z.infer<typeof TriggerDefinitionSchema>;
export const isV2 = (
definition: TriggerDefinition,
_rule: TriggerRuleV2 | TriggerRuleV1,
): _rule is TriggerRuleV2 => {
return definition.version === 2;
};
export const TriggerV2Schema = TriggerDefinitionV2Schema.extend({
id: z.string(),
});
export const TriggerSchema = z.discriminatedUnion('version', [
TriggerV1Schema,
TriggerV2Schema,
]);
export type Trigger = z.infer<typeof TriggerSchema>;
export type TriggerV2 = z.infer<typeof TriggerV2Schema>;
export type TriggerRuleV2 = z.infer<typeof TriggerRuleV2Schema>;
export type TriggerDefinitionV2 = z.infer<typeof TriggerDefinitionV2Schema>;
export type TriggerCondition = z.infer<typeof TriggerConditionSchema>;
export type Comparator = z.infer<typeof ComparatorSchema>;