testnet-laconicd-stack/cli/map-subscribers-to-participants.ts

140 lines
4.7 KiB
TypeScript

import * as fs from 'fs';
import * as crypto from 'crypto';
import * as path from 'path';
import yargs from 'yargs';
import { hideBin } from 'yargs/helpers';
import { parse as csvParse } from 'csv-parse';
import * as csvWriter from 'csv-writer';
import dotenv from 'dotenv';
import { StargateClient } from '@cosmjs/stargate';
import { Registry } from '@cerc-io/registry-sdk';
dotenv.config();
const LACONICD_GQL_ENDPOINT = process.env.LACONICD_GQL_ENDPOINT || 'https://laconicd.laconic.com/api';
const LACONICD_RPC_ENDPOINT = process.env.LACONICD_RPC_ENDPOINT || 'https://laconicd.laconic.com';
const LACONICD_CHAIN_ID = process.env.LACONICD_CHAIN_ID || 'laconic_9000-1';
async function main(): Promise<void> {
const argv = _getArgv();
const registry = new Registry(LACONICD_GQL_ENDPOINT, LACONICD_RPC_ENDPOINT, LACONICD_CHAIN_ID);
const client = await StargateClient.connect(LACONICD_RPC_ENDPOINT);
const participants = await registry.getParticipants();
const subscribers = await readSubscribers(argv.subscribersCsv);
await processSubscribers(client, participants, subscribers, argv.output);
}
async function readSubscribers(subscribersCsvPath: string): Promise<any> {
const fileContent = fs.readFileSync(subscribersCsvPath, { encoding: 'utf-8' });
const headers = ['subscriber_id', 'email', 'status', 'premium?', 'created_at', 'api_subscription_id'];
return csvParse(fileContent, { delimiter: ',', columns: headers }).toArray();
}
function hashSubscriberId(subscriberId: string): string {
return '0x' + crypto.createHash('sha256').update(subscriberId).digest('hex');
}
async function processSubscribers(client: StargateClient, participants: any[], subscribers: any[], outputPath: string) {
// Map kyc_id to participant data
const kycMap: Record<string, any> = {};
participants.forEach((participant: any) => {
kycMap[participant.kycId] = participant;
});
const onboardedSubscribers: any[] = [];
for (const subscriber of subscribers) {
const hashedSubscriberId = hashSubscriberId(subscriber['subscriber_id']);
const participant = kycMap[hashedSubscriberId];
if (!participant) {
continue;
}
const participantAddresss = participant['cosmosAddress'];
// Fetch participant's Laconic pubkey
const participantAccount = await client.getAccount(participantAddresss);
const participantPubkey = participantAccount?.pubkey;
// Skip participant if pubkey not found
// (account may have funds but hasn't done any tx till now)
if (!participantPubkey) {
continue;
}
// Skip participant if an onboarding tx not found
const onboardingTxs = await client.searchTx(`message.sender='${participantAddresss}' AND message.action='/cerc.onboarding.v1.MsgOnboardParticipant'`);
if (onboardingTxs.length === 0) {
continue;
}
const latestOnboardingTx = onboardingTxs.reduce((prev, current) => {
return current.height > prev.height ? current : prev;
});
const onboardedSubscriber = {
subscriber_id: subscriber['subscriber_id'],
email: subscriber['email'],
status: subscriber['status'],
'premium?': subscriber['premium?'],
created_at: subscriber['created_at'],
cosmos_address: participantAddresss,
nitro_address: participant['nitroAddress'],
role: participant['role'],
hashed_subscriber_id: participant['kycId'],
laconic_pubkey: participantPubkey.value,
onboarding_height: latestOnboardingTx.height
};
onboardedSubscribers.push(onboardedSubscriber);
}
const writer = csvWriter.createObjectCsvWriter({
path: path.resolve(outputPath),
header: [
{ id: 'subscriber_id', title: 'subscriber_id' },
{ id: 'email', title: 'email' },
{ id: 'status', title: 'status' },
{ id: 'premium?', title: 'premium?' },
{ id: 'created_at', title: 'created_at' },
{ id: 'cosmos_address', title: 'cosmos_address' },
{ id: 'nitro_address', title: 'nitro_address' },
{ id: 'role', title: 'role' },
{ id: 'hashed_subscriber_id', title: 'hashed_subscriber_id' },
{ id: 'laconic_pubkey', title: 'laconic_pubkey' },
{ id: 'onboarding_height', title: 'onboarding_height' },
],
alwaysQuote: true
});
await writer.writeRecords(onboardedSubscribers);
console.log(`Data has been written to ${path.resolve(outputPath)}`);
}
function _getArgv (): any {
return yargs(hideBin(process.argv))
.option('subscribersCsv', {
alias: 's',
type: 'string',
demandOption: true,
describe: 'Path to the subscribers CSV file',
})
.option('output', {
alias: 'o',
type: 'string',
demandOption: true,
describe: 'Path to the output CSV file',
})
.help()
.argv;
}
main().catch(err => {
console.log(err);
});