In HollaEx, user tiers are represented by verification_level, while KYC status is stored in id_data.status.
id_data.status values are typically:
-
0: no status
-
1: pending (KYC is being processed)
-
2: rejected
-
3: completed (approved)
There are two approaches to do this using Event-driven and Cron-based setup.
Event-driven (recommended on v2.17.1+)
Plugins can subscribe to the internal events channel:
toolsLib.database.subscriber.subscribe('channel:events');
toolsLib.database.subscriber.on('message', async (channel, message) => {
try {
if (channel === 'channel:events') {
const { type, data } = JSON.parse(message);
// For tier changes:
if (type === 'user' && data.action === 'update') {
loggerPlugin.info('/plugins/tier-upgrade-kyc/events update',
data.user_id,
data.email,
data.id_data);
// upgrade user's verification_level if all conditions are met
}
}
} catch (err) {
loggerPlugin.error('/plugins/tier-upgrade-kyc/events', err);
}
});
From there, you can implement your logic to upgrade the user automatically (e.g., when you confirm id_data.status === 3, or when your KYC provider marks the user approved), using:
await toolsLib.user.changeUserVerificationLevelById(userId, 2);
Cron-based approach
You can periodically scan for users who:
- are active (
activated: true)
- are not flagged (
flagged: false)
- are currently Tier 1 (
verification_level: 1)
- have approved KYC (
id_data.status: 3)
Example query:
const users = await toolsLib.database.findAll('user', {
where: {
activated: true,
flagged: false,
verification_level: 1,
id_data: {
status: 3
}
},
raw: true,
attributes: [
'id',
'email',
'network_id',
'id_data',
'verification_level',
'email_verified',
'activated',
'flagged',
'created_at'
]
});
Then loop through the matches and upgrade them:
await toolsLib.user.changeUserVerificationLevelById(user.id, 2);
Full example plugin (runs hourly for cron with hook-based setup)
'use strict';
const {
toolsLib,
loggerPlugin,
cron
} = this;
const EVENTS_CHANNEL = 'channel:events';
// Event-based approach
toolsLib.database.subscriber.subscribe(EVENTS_CHANNEL);
toolsLib.database.subscriber.on('message', async (channel, message) => {
try {
if (channel !== EVENTS_CHANNEL) return;
const { type, data } = JSON.parse(message);
// For tier changes:
if (type === 'user' && data && data.action === 'update') {
loggerPlugin.info(
'/plugins/tier-upgrade-kyc/events update',
data.user_id,
data.email,
data.id_data,
data.previous_user
);
// Extra guard: only upgrade if the user is still eligible (prevents loops / bad events)
const user = await toolsLib.database.findOne('user', {
where: { id: data.user_id },
raw: true,
attributes: ['id', 'email', 'id_data', 'verification_level', 'activated', 'flagged']
});
if (
user &&
user.activated === true &&
user.flagged === false &&
user.verification_level === 1 &&
user.id_data &&
user.id_data.status === 3
) {
loggerPlugin.verbose(
'/plugins/tier-upgrade-kyc/events',
`Upgrading user ${user.id} (${user.email}) -> level 2`
);
await toolsLib.user.changeUserVerificationLevelById(user.id, 2);
}
}
} catch (err) {
loggerPlugin.error('/plugins/tier-upgrade-kyc/events', err);
}
});
// Cron-based approach
const run = async () => {
loggerPlugin.verbose('/plugins/tier-upgrade-kyc/run', 'Running tier upgrade KYC plugin');
const users = await toolsLib.database.findAll('user', {
where: {
activated: true,
flagged: false,
verification_level: 1,
id_data: {
status: 3
}
},
raw: true,
attributes: [
'id',
'email',
'network_id',
'id_data',
'verification_level',
'email_verified',
'activated',
'flagged',
'created_at'
]
});
loggerPlugin.verbose('/plugins/tier-upgrade-kyc/run', `Found ${users.length} users to upgrade`);
if (!users.length) return;
for (const user of users) {
loggerPlugin.verbose('/plugins/tier-upgrade-kyc/run', `Upgrading user ${user.id} (${user.email}) -> level 2`);
await toolsLib.user.changeUserVerificationLevelById(user.id, 2);
loggerPlugin.verbose('/plugins/tier-upgrade-kyc/run', `User ${user.id} upgraded successfully`);
}
};
run();
// Runs at minute 0 of every hour (UTC)
const task = cron.schedule(
'0 * * * *',
() => run(),
{ timezone: 'Etc/UTC' }
);
task.start();