From ba64d232bb4257b25a815334ea2d277d856acaa5 Mon Sep 17 00:00:00 2001
From: Hugo Trentesaux <hugo@trentesaux.fr>
Date: Wed, 6 Dec 2023 15:51:51 +0100
Subject: [PATCH] [feat] add smith certs by copy-pasting certs

---
 README.md                                     |  11 +-
 ...14613514-Data.js => 1701873131980-Data.js} |  24 ++-
 schema.graphql                                |  18 ++
 src/genesis.ts                                |   8 +-
 src/main.ts                                   | 169 +++++++++++++++++-
 src/model/generated/index.ts                  |   3 +
 src/model/generated/smithCert.model.ts        |  23 ++-
 .../generated/smithCertCreation.model.ts      |  19 ++
 src/model/generated/smithCertRemoval.model.ts |  19 ++
 src/model/generated/smithCertRenewal.model.ts |  19 ++
 10 files changed, 305 insertions(+), 8 deletions(-)
 rename db/migrations/{1701814613514-Data.js => 1701873131980-Data.js} (88%)
 create mode 100644 src/model/generated/smithCertCreation.model.ts
 create mode 100644 src/model/generated/smithCertRemoval.model.ts
 create mode 100644 src/model/generated/smithCertRenewal.model.ts

diff --git a/README.md b/README.md
index cd0ba00..5fd76e2 100644
--- a/README.md
+++ b/README.md
@@ -120,10 +120,17 @@ See https://duniter.org/wiki/duniter-v2/indexers/duniter-squid/
   - [ ] membership.MembershipRequested
   - [ ] membership.MembershipRevoked
   - [ ] membership.PendingMembershipExpired
+  - [ ] smithMembership.MembershipAcquired
+  - [ ] smithMembership.MembershipExpired
+  - [ ] smithMembership.MembershipRenewed
+  - [ ] smithMembership.MembershipRequested
+  - [ ] smithMembership.MembershipRevoked
+  - [ ] smithMembership.PendingMembershipExpired
   - [x] account.AccountLinked
   - [x] account.AccountUnlinked
   - [x] cert.NewCert TODO if the cert is already existing, what to do?
   - [x] cert.RenewedCert
   - [x] cert.RemovedCert
-  - [ ] smithCert.NewCert
-  - [ ] smithCert.RenewedCert
+  - [x] smithCert.NewCert
+  - [x] smithCert.RenewedCert
+  - [x] smithCert.RemovedCert
diff --git a/db/migrations/1701814613514-Data.js b/db/migrations/1701873131980-Data.js
similarity index 88%
rename from db/migrations/1701814613514-Data.js
rename to db/migrations/1701873131980-Data.js
index 26e545b..5daca7d 100644
--- a/db/migrations/1701814613514-Data.js
+++ b/db/migrations/1701873131980-Data.js
@@ -1,5 +1,5 @@
-module.exports = class Data1701814613514 {
-    name = 'Data1701814613514'
+module.exports = class Data1701873131980 {
+    name = 'Data1701873131980'
 
     async up(db) {
         await db.query(`CREATE TABLE "event" ("id" character varying NOT NULL, "index" integer NOT NULL, "phase" text NOT NULL, "pallet" text NOT NULL, "name" text NOT NULL, "args" jsonb, "args_str" text array, "block_id" character varying, "extrinsic_id" character varying, "call_id" character varying, CONSTRAINT "PK_30c2f3bbaf6d34a55f8ae6e4614" PRIMARY KEY ("id"))`)
@@ -47,7 +47,13 @@ module.exports = class Data1701814613514 {
         await db.query(`CREATE TABLE "cert" ("id" character varying NOT NULL, "active" boolean NOT NULL, "created_on" integer NOT NULL, "expire_on" integer NOT NULL, "issuer_id" character varying, "receiver_id" character varying, CONSTRAINT "PK_6a0ce80cc860598b4f16c00998c" PRIMARY KEY ("id"))`)
         await db.query(`CREATE INDEX "IDX_70592e488b2e75cd8a2fa79826" ON "cert" ("issuer_id") `)
         await db.query(`CREATE INDEX "IDX_262e29ab91c8ebc727cc518f2f" ON "cert" ("receiver_id") `)
-        await db.query(`CREATE TABLE "smith_cert" ("id" character varying NOT NULL, "issuer_id" character varying, "receiver_id" character varying, CONSTRAINT "PK_ae2ef36c9f6d40348c86230fd35" PRIMARY KEY ("id"))`)
+        await db.query(`CREATE TABLE "smith_cert_creation" ("id" character varying NOT NULL, "block_number" integer NOT NULL, "cert_id" character varying, CONSTRAINT "PK_8216944178a0c558d9d2936125b" PRIMARY KEY ("id"))`)
+        await db.query(`CREATE INDEX "IDX_4ce4665e97ce9d1f711ea638c3" ON "smith_cert_creation" ("cert_id") `)
+        await db.query(`CREATE TABLE "smith_cert_renewal" ("id" character varying NOT NULL, "block_number" integer NOT NULL, "cert_id" character varying, CONSTRAINT "PK_163e552bb48db2b26dc20dde54b" PRIMARY KEY ("id"))`)
+        await db.query(`CREATE INDEX "IDX_e5ad5606c3f0bc6d67653496f6" ON "smith_cert_renewal" ("cert_id") `)
+        await db.query(`CREATE TABLE "smith_cert_removal" ("id" character varying NOT NULL, "block_number" integer NOT NULL, "cert_id" character varying, CONSTRAINT "PK_3f66d1fbae04ed3095e48d223b7" PRIMARY KEY ("id"))`)
+        await db.query(`CREATE INDEX "IDX_2cdaac89065f75d23428631378" ON "smith_cert_removal" ("cert_id") `)
+        await db.query(`CREATE TABLE "smith_cert" ("id" character varying NOT NULL, "active" boolean NOT NULL, "created_on" integer NOT NULL, "expire_on" integer NOT NULL, "issuer_id" character varying, "receiver_id" character varying, CONSTRAINT "PK_ae2ef36c9f6d40348c86230fd35" PRIMARY KEY ("id"))`)
         await db.query(`CREATE INDEX "IDX_ae67cbd087fcea0e1ec2f70cd0" ON "smith_cert" ("issuer_id") `)
         await db.query(`CREATE INDEX "IDX_5e414c1d12af16165881a16b63" ON "smith_cert" ("receiver_id") `)
         await db.query(`CREATE TABLE "change_owner_key" ("id" character varying NOT NULL, "block_number" integer NOT NULL, "identity_id" character varying, "previous_id" character varying, "next_id" character varying, CONSTRAINT "PK_bc0eb6d98434c5b8b2250752395" PRIMARY KEY ("id"))`)
@@ -81,6 +87,9 @@ module.exports = class Data1701814613514 {
         await db.query(`ALTER TABLE "cert_removal" ADD CONSTRAINT "FK_2efb6397676a3b82bde7631ed03" FOREIGN KEY ("cert_id") REFERENCES "cert"("id") ON DELETE NO ACTION ON UPDATE NO ACTION`)
         await db.query(`ALTER TABLE "cert" ADD CONSTRAINT "FK_70592e488b2e75cd8a2fa798261" FOREIGN KEY ("issuer_id") REFERENCES "identity"("id") ON DELETE NO ACTION ON UPDATE NO ACTION`)
         await db.query(`ALTER TABLE "cert" ADD CONSTRAINT "FK_262e29ab91c8ebc727cc518f2fb" FOREIGN KEY ("receiver_id") REFERENCES "identity"("id") ON DELETE NO ACTION ON UPDATE NO ACTION`)
+        await db.query(`ALTER TABLE "smith_cert_creation" ADD CONSTRAINT "FK_4ce4665e97ce9d1f711ea638c35" FOREIGN KEY ("cert_id") REFERENCES "smith_cert"("id") ON DELETE NO ACTION ON UPDATE NO ACTION`)
+        await db.query(`ALTER TABLE "smith_cert_renewal" ADD CONSTRAINT "FK_e5ad5606c3f0bc6d67653496f6b" FOREIGN KEY ("cert_id") REFERENCES "smith_cert"("id") ON DELETE NO ACTION ON UPDATE NO ACTION`)
+        await db.query(`ALTER TABLE "smith_cert_removal" ADD CONSTRAINT "FK_2cdaac89065f75d234286313789" FOREIGN KEY ("cert_id") REFERENCES "smith_cert"("id") ON DELETE NO ACTION ON UPDATE NO ACTION`)
         await db.query(`ALTER TABLE "smith_cert" ADD CONSTRAINT "FK_ae67cbd087fcea0e1ec2f70cd04" FOREIGN KEY ("issuer_id") REFERENCES "identity"("id") ON DELETE NO ACTION ON UPDATE NO ACTION`)
         await db.query(`ALTER TABLE "smith_cert" ADD CONSTRAINT "FK_5e414c1d12af16165881a16b638" FOREIGN KEY ("receiver_id") REFERENCES "identity"("id") ON DELETE NO ACTION ON UPDATE NO ACTION`)
         await db.query(`ALTER TABLE "change_owner_key" ADD CONSTRAINT "FK_af577baa612d86d98a1ae583438" FOREIGN KEY ("identity_id") REFERENCES "identity"("id") ON DELETE NO ACTION ON UPDATE NO ACTION`)
@@ -138,6 +147,12 @@ module.exports = class Data1701814613514 {
         await db.query(`DROP TABLE "cert"`)
         await db.query(`DROP INDEX "public"."IDX_70592e488b2e75cd8a2fa79826"`)
         await db.query(`DROP INDEX "public"."IDX_262e29ab91c8ebc727cc518f2f"`)
+        await db.query(`DROP TABLE "smith_cert_creation"`)
+        await db.query(`DROP INDEX "public"."IDX_4ce4665e97ce9d1f711ea638c3"`)
+        await db.query(`DROP TABLE "smith_cert_renewal"`)
+        await db.query(`DROP INDEX "public"."IDX_e5ad5606c3f0bc6d67653496f6"`)
+        await db.query(`DROP TABLE "smith_cert_removal"`)
+        await db.query(`DROP INDEX "public"."IDX_2cdaac89065f75d23428631378"`)
         await db.query(`DROP TABLE "smith_cert"`)
         await db.query(`DROP INDEX "public"."IDX_ae67cbd087fcea0e1ec2f70cd0"`)
         await db.query(`DROP INDEX "public"."IDX_5e414c1d12af16165881a16b63"`)
@@ -172,6 +187,9 @@ module.exports = class Data1701814613514 {
         await db.query(`ALTER TABLE "cert_removal" DROP CONSTRAINT "FK_2efb6397676a3b82bde7631ed03"`)
         await db.query(`ALTER TABLE "cert" DROP CONSTRAINT "FK_70592e488b2e75cd8a2fa798261"`)
         await db.query(`ALTER TABLE "cert" DROP CONSTRAINT "FK_262e29ab91c8ebc727cc518f2fb"`)
+        await db.query(`ALTER TABLE "smith_cert_creation" DROP CONSTRAINT "FK_4ce4665e97ce9d1f711ea638c35"`)
+        await db.query(`ALTER TABLE "smith_cert_renewal" DROP CONSTRAINT "FK_e5ad5606c3f0bc6d67653496f6b"`)
+        await db.query(`ALTER TABLE "smith_cert_removal" DROP CONSTRAINT "FK_2cdaac89065f75d234286313789"`)
         await db.query(`ALTER TABLE "smith_cert" DROP CONSTRAINT "FK_ae67cbd087fcea0e1ec2f70cd04"`)
         await db.query(`ALTER TABLE "smith_cert" DROP CONSTRAINT "FK_5e414c1d12af16165881a16b638"`)
         await db.query(`ALTER TABLE "change_owner_key" DROP CONSTRAINT "FK_af577baa612d86d98a1ae583438"`)
diff --git a/schema.graphql b/schema.graphql
index 29d7a91..a1d52cf 100644
--- a/schema.graphql
+++ b/schema.graphql
@@ -207,6 +207,24 @@ type CertRemoval @entity {
 type SmithCert @entity {
   issuer: Identity!
   receiver: Identity!
+  active: Boolean!
+  createdOn: Int!
+  expireOn: Int!
+  creation: [SmithCertCreation!] @derivedFrom(field: "cert")
+  renewal: [SmithCertRenewal!] @derivedFrom(field: "cert")
+  removal: [SmithCertRemoval!] @derivedFrom(field: "cert")
+}
+type SmithCertCreation @entity {
+  cert: SmithCert! @index
+  blockNumber: Int!
+}
+type SmithCertRenewal @entity {
+  cert: SmithCert! @index
+  blockNumber: Int!
+}
+type SmithCertRemoval @entity {
+  cert: SmithCert! @index
+  blockNumber: Int!
 }
 
 "Membership"
diff --git a/src/genesis.ts b/src/genesis.ts
index d959e6b..5a3e9b3 100644
--- a/src/genesis.ts
+++ b/src/genesis.ts
@@ -2,6 +2,7 @@ import type { Address, IdtyIndex, Ctx } from "./main";
 import { readFileSync } from "fs";
 import { Account, Cert, SmithCert, Identity, Membership, SmithMembership, Transfer, ChangeOwnerKey } from "./model";
 import path from "path/posix";
+import { constants } from "./types";
 
 // define genesis interfaces
 // duniter build-spec --chain gdev_dev 1> ./specs.json
@@ -121,7 +122,7 @@ export async function saveGenesis(ctx: Ctx) {
       let old_account = accounts.get(old_account_id);
       if (old_account == null) {
         // this can happen if the old account is emptied from its content
-        old_account = new Account({id: old_account_id});
+        old_account = new Account({ id: old_account_id });
         accounts.set(old_account_id, old_account);
       }
       chok.push(
@@ -156,11 +157,16 @@ export async function saveGenesis(ctx: Ctx) {
   // collect smith certs
   for (const [receiver_index, certs_received] of Object.entries(genesis.smithCert.certsByReceiver)) {
     for (const [issuer_index, expiration_block] of Object.entries(certs_received)) {
+      // WARN null smith cert expiration block
+      let non_null_expiration_block: number = expiration_block as number ?? 2102400;
       smithCerts.push(
         new SmithCert({
           id: `genesis-${issuer_index}-${receiver_index}`,
+          active: true,
           issuer: identities.get(parseInt(issuer_index)),
           receiver: identities.get(parseInt(receiver_index)),
+          createdOn: 0,
+          expireOn: non_null_expiration_block,
         })
       );
     }
diff --git a/src/main.ts b/src/main.ts
index 174a82c..99f5cf4 100644
--- a/src/main.ts
+++ b/src/main.ts
@@ -3,7 +3,20 @@ import { StoreWithCache, TypeormDatabaseWithCache } from "@belopash/typeorm-stor
 import * as ss58 from "@subsquid/ss58";
 import assert from "assert";
 import { processor, ProcessorContext } from "./processor";
-import { Account, Cert, CertCreation, CertRemoval, CertRenewal, ChangeOwnerKey, Identity, Transfer } from "./model";
+import {
+  Account,
+  Cert,
+  CertCreation,
+  CertRemoval,
+  CertRenewal,
+  SmithCert,
+  SmithCertCreation,
+  SmithCertRemoval,
+  SmithCertRenewal,
+  ChangeOwnerKey,
+  Identity,
+  Transfer,
+} from "./model";
 import { events as events_t, calls as calls_t, constants } from "./types";
 import { saveBlock, saveExtrinsic, saveCall, saveEvent } from "./giant-squid";
 import { saveGenesis } from "./genesis";
@@ -45,6 +58,9 @@ processor.run(new TypeormDatabaseWithCache(), async (ctx) => {
     certCreation: [],
     certRenewal: [],
     certRemoval: [],
+    smithCertCreation: [],
+    smithCertRenewal: [],
+    smithCertRemoval: [],
     accountLink: [],
     accountUnlink: [],
   };
@@ -60,6 +76,10 @@ processor.run(new TypeormDatabaseWithCache(), async (ctx) => {
     certCreation: [],
     certRenewal: [],
     certRemoval: [],
+    smithCert: new Map(),
+    smithCertCreation: [],
+    smithCertRenewal: [],
+    smithCertRemoval: [],
   };
   await prepareData(ctx, ndata, data);
 
@@ -97,6 +117,9 @@ interface NewData {
   certCreation: CertCreationEvent[];
   certRenewal: CertRenewalEvent[];
   certRemoval: CertRemovalEvent[];
+  smithCertCreation: CertCreationEvent[];
+  smithCertRenewal: CertRenewalEvent[];
+  smithCertRemoval: CertRemovalEvent[];
   accountLink: AccountLinkEvent[];
   accountUnlink: AccountUnlinkEvent[];
 }
@@ -163,6 +186,10 @@ interface Data {
   certCreation: CertCreation[];
   certRenewal: CertRenewal[];
   certRemoval: CertRemoval[];
+  smithCert: Map<[IdtyIndex, IdtyIndex], Cert>;
+  smithCertCreation: CertCreation[];
+  smithCertRenewal: CertRenewal[];
+  smithCertRemoval: CertRemoval[];
 }
 
 // ================================================================================
@@ -224,6 +251,7 @@ function getNewData(ctx: Ctx, ndata: NewData) {
           });
           break;
 
+        // ====================================================== Identity
         // ===== Identity.IdtyCreated
         case events_t.identity.idtyCreated.name:
           let newI: { idtyIndex: IdtyIndex; ownerKey: Address };
@@ -327,6 +355,64 @@ function getNewData(ctx: Ctx, ndata: NewData) {
           });
           break;
 
+        // ====================================================== SmithCert
+        // ===== SmithCert.NewCert
+        case events_t.smithCert.newCert.name:
+          let scert: { issuer: IdtyIndex; receiver: IdtyIndex };
+          if (events_t.smithCert.newCert.v700.is(event)) {
+            scert = events_t.smithCert.newCert.v700.decode(event);
+          } else {
+            throw new Error("Unsupported spec");
+          }
+          ndata.smithCertCreation.push({
+            id: event.id, // the id of the cert will be this forever
+            issuerId: scert.issuer,
+            receiverId: scert.receiver,
+            createdOn: block.header.height,
+            expireOn: block.header.height + constants.smithCert.validityPeriod.v700.get(event.block),
+          });
+          break;
+
+        // ===== SmithCert.RenewCert
+        case events_t.smithCert.renewedCert.name:
+          let srecert: { issuer: IdtyIndex; receiver: IdtyIndex };
+          if (events_t.smithCert.renewedCert.v700.is(event)) {
+            srecert = events_t.smithCert.renewedCert.v700.decode(event);
+          } else {
+            throw new Error("Unsupported spec");
+          }
+          ndata.smithCertRenewal.push({
+            id: event.id,
+            issuerId: srecert.issuer,
+            receiverId: srecert.receiver,
+            blockNumber: block.header.height,
+            expireOn: block.header.height + constants.smithCert.validityPeriod.v700.get(event.block),
+          });
+          break;
+
+        // ===== SmithCert.RemovedCert
+        case events_t.smithCert.removedCert.name:
+          let sremcert: {
+            issuer: IdtyIndex;
+            issuerIssuedCount: number;
+            receiver: IdtyIndex;
+            receiverReceivedCount: number;
+            expiration: boolean;
+          };
+          if (events_t.smithCert.removedCert.v700.is(event)) {
+            sremcert = events_t.smithCert.removedCert.v700.decode(event);
+          } else {
+            throw new Error("Unsupported spec");
+          }
+          ndata.smithCertRemoval.push({
+            id: event.id,
+            issuerId: sremcert.issuer,
+            receiverId: sremcert.receiver,
+            blockNumber: block.header.height,
+          });
+          break;
+
+        // ====================================================== Account
         // ===== Account.AccountLinked
         case events_t.account.accountLinked.name:
           let acclink: { who: Address; identity: IdtyIndex };
@@ -373,6 +459,9 @@ async function prepareData(ctx: Ctx, newData: NewData, data: Data) {
   await createCerts(ctx, newData, data);
   await createCertRenewals(ctx, newData, data);
   await createCertRemovals(ctx, newData, data);
+  await createSmithCerts(ctx, newData, data);
+  await createSmithCertRenewals(ctx, newData, data);
+  await createSmithCertRemovals(ctx, newData, data);
   await updateAccountLinks(ctx, newData, data);
   await updateAccountUnlinks(ctx, newData, data);
 }
@@ -441,6 +530,7 @@ async function storeData(ctx: Ctx, data: Data) {
   await ctx.store.upsert([...data.identities.values()]);
   // certs can have been changed (renewed, removed...)
   await ctx.store.upsert([...data.cert.values()]);
+  await ctx.store.upsert([...data.smithCert.values()]);
 
   // INSERT = these object can not exist before
   await ctx.store.insert(data.transfers);
@@ -448,6 +538,9 @@ async function storeData(ctx: Ctx, data: Data) {
   await ctx.store.insert(data.certCreation);
   await ctx.store.insert(data.certRenewal);
   await ctx.store.insert(data.certRemoval);
+  await ctx.store.insert(data.smithCertCreation);
+  await ctx.store.insert(data.smithCertRenewal);
+  await ctx.store.insert(data.smithCertRemoval);
 }
 
 // =============================================================================================================
@@ -627,6 +720,80 @@ async function createCertRemovals(ctx: Ctx, newData: NewData, data: Data) {
   }
 }
 
+// copy of non-smith version
+async function createSmithCerts(ctx: Ctx, newData: NewData, data: Data) {
+  for (let c of newData.smithCertCreation) {
+    let { id, issuerId, receiverId, createdOn, expireOn } = c;
+    let cert = await ctx.store.findOne(SmithCert, {
+      relations: { issuer: true, receiver: true },
+      where: { issuer: { index: issuerId }, receiver: { index: receiverId } },
+    });
+    if (cert == null) {
+      let issuer = await getIdtyByIndexOrFail(ctx, data, issuerId);
+      let receiver = await getIdtyByIndexOrFail(ctx, data, receiverId);
+      cert = new SmithCert({
+        id,
+        active: true,
+        issuer,
+        receiver,
+        createdOn,
+        expireOn,
+      });
+    } else {
+      cert.active = true;
+      cert.createdOn = createdOn;
+      cert.expireOn = expireOn;
+    }
+    data.cert.set([issuerId, receiverId], cert);
+    data.certCreation.push(
+      new SmithCertCreation({
+        id,
+        cert,
+        blockNumber: createdOn,
+      })
+    );
+  }
+}
+// copy of non-smith version
+async function createSmithCertRenewals(ctx: Ctx, newData: NewData, data: Data) {
+  for (let c of newData.smithCertRenewal) {
+    let { id, issuerId, receiverId, blockNumber, expireOn } = c;
+    let cert = await ctx.store.findOneOrFail(SmithCert, {
+      relations: { issuer: true, receiver: true },
+      where: { issuer: { index: issuerId }, receiver: { index: receiverId } },
+    });
+    cert.expireOn = expireOn;
+    data.cert.set([issuerId, receiverId], cert);
+    data.certRenewal.push(
+      new SmithCertRenewal({
+        id,
+        cert,
+        blockNumber,
+      })
+    );
+  }
+}
+// copy of non-smith version
+async function createSmithCertRemovals(ctx: Ctx, newData: NewData, data: Data) {
+  for (let c of newData.smithCertRemoval) {
+    let { id, issuerId, receiverId, blockNumber } = c;
+    let cert = await ctx.store.findOneOrFail(SmithCert, {
+      relations: { issuer: true, receiver: true },
+      where: { issuer: { index: issuerId }, receiver: { index: receiverId } },
+    });
+    cert.active = false;
+    cert.expireOn = blockNumber;
+    data.cert.set([issuerId, receiverId], cert);
+    data.certRenewal.push(
+      new CertRemoval({
+        id,
+        cert,
+        blockNumber,
+      })
+    );
+  }
+}
+
 async function updateAccountLinks(ctx: Ctx, newData: NewData, data: Data) {
   for (let l of newData.accountLink) {
     // we can link an identity to a non-existing account
diff --git a/src/model/generated/index.ts b/src/model/generated/index.ts
index 92ffc73..f83dc7f 100644
--- a/src/model/generated/index.ts
+++ b/src/model/generated/index.ts
@@ -15,5 +15,8 @@ export * from "./certCreation.model"
 export * from "./certRenewal.model"
 export * from "./certRemoval.model"
 export * from "./smithCert.model"
+export * from "./smithCertCreation.model"
+export * from "./smithCertRenewal.model"
+export * from "./smithCertRemoval.model"
 export * from "./membership.model"
 export * from "./smithMembership.model"
diff --git a/src/model/generated/smithCert.model.ts b/src/model/generated/smithCert.model.ts
index 1a22caa..8e546c5 100644
--- a/src/model/generated/smithCert.model.ts
+++ b/src/model/generated/smithCert.model.ts
@@ -1,5 +1,8 @@
-import {Entity as Entity_, Column as Column_, PrimaryColumn as PrimaryColumn_, ManyToOne as ManyToOne_, Index as Index_} from "typeorm"
+import {Entity as Entity_, Column as Column_, PrimaryColumn as PrimaryColumn_, ManyToOne as ManyToOne_, Index as Index_, OneToMany as OneToMany_} from "typeorm"
 import {Identity} from "./identity.model"
+import {SmithCertCreation} from "./smithCertCreation.model"
+import {SmithCertRenewal} from "./smithCertRenewal.model"
+import {SmithCertRemoval} from "./smithCertRemoval.model"
 
 /**
  * Smith certification
@@ -20,4 +23,22 @@ export class SmithCert {
     @Index_()
     @ManyToOne_(() => Identity, {nullable: true})
     receiver!: Identity
+
+    @Column_("bool", {nullable: false})
+    active!: boolean
+
+    @Column_("int4", {nullable: false})
+    createdOn!: number
+
+    @Column_("int4", {nullable: false})
+    expireOn!: number
+
+    @OneToMany_(() => SmithCertCreation, e => e.cert)
+    creation!: SmithCertCreation[]
+
+    @OneToMany_(() => SmithCertRenewal, e => e.cert)
+    renewal!: SmithCertRenewal[]
+
+    @OneToMany_(() => SmithCertRemoval, e => e.cert)
+    removal!: SmithCertRemoval[]
 }
diff --git a/src/model/generated/smithCertCreation.model.ts b/src/model/generated/smithCertCreation.model.ts
new file mode 100644
index 0000000..501792f
--- /dev/null
+++ b/src/model/generated/smithCertCreation.model.ts
@@ -0,0 +1,19 @@
+import {Entity as Entity_, Column as Column_, PrimaryColumn as PrimaryColumn_, ManyToOne as ManyToOne_, Index as Index_} from "typeorm"
+import {SmithCert} from "./smithCert.model"
+
+@Entity_()
+export class SmithCertCreation {
+    constructor(props?: Partial<SmithCertCreation>) {
+        Object.assign(this, props)
+    }
+
+    @PrimaryColumn_()
+    id!: string
+
+    @Index_()
+    @ManyToOne_(() => SmithCert, {nullable: true})
+    cert!: SmithCert
+
+    @Column_("int4", {nullable: false})
+    blockNumber!: number
+}
diff --git a/src/model/generated/smithCertRemoval.model.ts b/src/model/generated/smithCertRemoval.model.ts
new file mode 100644
index 0000000..91f6290
--- /dev/null
+++ b/src/model/generated/smithCertRemoval.model.ts
@@ -0,0 +1,19 @@
+import {Entity as Entity_, Column as Column_, PrimaryColumn as PrimaryColumn_, ManyToOne as ManyToOne_, Index as Index_} from "typeorm"
+import {SmithCert} from "./smithCert.model"
+
+@Entity_()
+export class SmithCertRemoval {
+    constructor(props?: Partial<SmithCertRemoval>) {
+        Object.assign(this, props)
+    }
+
+    @PrimaryColumn_()
+    id!: string
+
+    @Index_()
+    @ManyToOne_(() => SmithCert, {nullable: true})
+    cert!: SmithCert
+
+    @Column_("int4", {nullable: false})
+    blockNumber!: number
+}
diff --git a/src/model/generated/smithCertRenewal.model.ts b/src/model/generated/smithCertRenewal.model.ts
new file mode 100644
index 0000000..c3b706b
--- /dev/null
+++ b/src/model/generated/smithCertRenewal.model.ts
@@ -0,0 +1,19 @@
+import {Entity as Entity_, Column as Column_, PrimaryColumn as PrimaryColumn_, ManyToOne as ManyToOne_, Index as Index_} from "typeorm"
+import {SmithCert} from "./smithCert.model"
+
+@Entity_()
+export class SmithCertRenewal {
+    constructor(props?: Partial<SmithCertRenewal>) {
+        Object.assign(this, props)
+    }
+
+    @PrimaryColumn_()
+    id!: string
+
+    @Index_()
+    @ManyToOne_(() => SmithCert, {nullable: true})
+    cert!: SmithCert
+
+    @Column_("int4", {nullable: false})
+    blockNumber!: number
+}
-- 
GitLab