Skip to content

About

An open-source Node.js module that serves as a reference for how KYC data should be named, stored, kept and logged under Armenian rules

Topics

Resources

Contributing

Security policy

Stars

4 stars

Watchers

0 watching

Forks

Repository files navigation

OpenCasp-KYC

KYC for Armenian crypto-asset service providers (CASPs) and brokers, by the book.

OpenCasp-KYC is a small, dependency-free Node.js module for teams that already have engineers and want to know exactly what customer data Armenian rules require, what to call it, how long to keep it, and what may appear in logs. Every field, deadline and retention period points at the article of the Armenian law or Central Bank act it comes from.

It includes a Sumsub adapter (bring your own keys), but the core does not depend on any provider.

Not legal advice. This package encodes a reading of public legal texts, listed with versions in SOURCES.md. Laws change and some rules are drafts or interpretations — each is labelled. Review your implementation with Armenian counsel and your AML/CFT officer.

What is inside

Module What it gives you Main sources
Source register 58 rules with law, article, short quote, status and verification date AML/CFT Law, Non-Cash Operations Law, CBA Decision 12-N, Personal Data Law
Data dictionary Stable snake_case field names with Armenian, Russian and English labels, per customer type, with PII class AML/CFT Law Art. 16(4), 16(5)–(7), 18(4)
CDD Level decision (simplified / standard / enhanced), validation, 7-day verification deadline, yearly review, CDD triggers, the AMD 300,000 cash rule Art. 3, 16, 17, 18, 27; Non-Cash Operations Law Art. 6.1
Retention Keep-until dates per record class; purge eligibility with legal holds Art. 22; Decision 12-N points 4, 7, 8, 12
Audit chain Append-only, hash-chained KYC events; log-safe summaries that never carry personal data Art. 22; draft Rule 7/07 §34; Personal Data Law Art. 5
Storage KycStore interface, in-memory and JSONL stores, KycLedger —
CBA records Decision 12-N transaction and customer records, report rows with the prescribed Armenian values Decision 12-N points 3, 5, 24, 31
Travel rule Crypto-transfer information check with the reject rule Art. 20.1, 27
Sumsub Signed client, webhook verification, applicant → dictionary mapping with gap report, recommended levels and an Armenian questionnaire Art. 16(8) (reliance), Personal Data Law Art. 13, 14, 27

Install

npm install opencasp-kyc

Node.js 20 or newer. No runtime dependencies.

Quick start

import { determineCddLevel, validateCustomer, retentionUntil, KycLedger, MemoryStore, auditSummary } from "opencasp-kyc";

// 1. Decide the CDD level from risk facts (AML/CFT Law Art. 18).
const { level } = determineCddLevel({ pep: false, nonCompliantCountry: false });

// 2. Validate what you collected (Art. 16(4)(1) for a natural person).
const result = validateCustomer(
  {
    customer_type: "natural_person",
    customer_id: "C-000123",
    first_name: "Anna",
    last_name: "Petrosyan",
    citizenship: "AM",
    date_of_birth: "1990-02-03",
    place_of_birth: "Yerevan",
    residence_address: "12 Abovyan St, Yerevan",
    residency_status: "resident",
    id_document_type: "passport",
    id_document_number: "AB1234567",
    id_document_issue_date: "2020-05-01",
    id_document_copy: "vault://docs/C-000123/passport.jpg",
    pep_status: "not_pep",
    business_profile: "Software engineer, ~500,000 AMD/month",
    purpose_and_nature: "Buying and holding BTC",
    verification_method: "non_face_to_face",
    identity_verified_at: "2026-09-20",
  },
  { level },
);
if (!result.ok) console.log(result.issues); // each issue lists the rule ids behind it

// 3. Record decisions in a tamper-evident chain; log only the safe summary.
const ledger = new KycLedger(new MemoryStore());
const ev = await ledger.append({
  type: "applicant.status_changed",
  actor: "compliance@yourcasp.am",
  actor_role: "compliance",
  customer_ref: "C-000123",
  applicant_ref: null,
  payload: { kyc_status: "approved", id_document_number: "AB1234567" },
});
console.log(auditSummary(ev)); // { …, fields: { kyc_status: "approved" }, redacted: ["id_document_number"] }

// 4. Know when you may erase (Art. 22(2): 5 years after the relationship ends).
retentionUntil("customer_identification", { relationshipEndedAt: "2026-03-31" }).keepUntil; // 2031-03-31

With Sumsub

import { SumsubClient, verifyWebhookDigest, parseWebhook, toDictionary } from "opencasp-kyc/sumsub";

const sumsub = new SumsubClient({ appToken: process.env.SUMSUB_APP_TOKEN!, appSecret: process.env.SUMSUB_APP_SECRET! });

// Create (or reuse) the applicant and a hosted link for the customer.
await sumsub.ensureApplicant("am-individual-standard", { externalUserId: "C-000123", email: "anna@example.am" });
const { url } = await sumsub.createLink("am-individual-standard", { externalUserId: "C-000123" });

// In your webhook handler — pass the raw body, not re-serialised JSON.
if (!verifyWebhookDigest(rawBody, req.headers, process.env.SUMSUB_WEBHOOK_SECRET!)) return reply(401);
const hook = parseWebhook(rawBody);
const applicant = await sumsub.getApplicant(hook.applicantId); // the applicant, not the webhook, is your record
const { values, gaps } = toDictionary(applicant, { level: "standard" });
// gaps lists Armenian-required fields Sumsub did not provide, with how to fill each.

A complete runnable server is in examples/node-webhook. The recommended Sumsub levels and the Armenian questionnaire are in docs/sumsub-levels.md; the full setup walkthrough is the Open Dram blog post "Sumsub for Armenian CASPs, end to end" (/blog/sumsub-armenia-end-to-end on the Open Dram website).

Things teams often get wrong

  • Beneficial ownership starts at 20% in Armenia (Art. 3(1)(14)), not 25%. Change the default in your KYB tool.
  • Place of birth and document issue date are required (Art. 16(4)(1)). Many KYC flows skip them.
  • Using Sumsub does not move responsibility (Art. 16(8)). You must be able to produce the data and document copies on request — keep your own copy (Decision 12-N point 4).
  • Simplified CDD does not mean less identification data in the current law; it reduces ongoing monitoring (Art. 3(1)(24)).
  • Selfie and liveness process biometric data: collect consent (Personal Data Law Art. 13). Sumsub is your processor and needs a written assignment/DPA (Art. 14); data leaves Armenia, so check Art. 27.
  • Logs are not a records system. Keep the chain; send only auditSummary() to application logs.

Documentation

Contributing

Corrections to the legal reading are the most valuable contributions. Please cite the article and the document version. See CONTRIBUTING.md.

License

Apache License 2.0 — see LICENSE and NOTICE.

About

An open-source Node.js module that serves as a reference for how KYC data should be named, stored, kept and logged under Armenian rules

Topics

Resources

Contributing

Security policy

Stars

4 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages