diff --git a/apps/shared/package.json b/apps/shared/package.json
index 8bd91aba98c275d2a099f75f9054d8647874cc54..e092ae552055278ec57d94efd8a8623dd2c7fccf 100644
--- a/apps/shared/package.json
+++ b/apps/shared/package.json
@@ -21,6 +21,7 @@
     "test:e2e": "jest --config ./test/jest.config.js"
   },
   "dependencies": {
+    "@aries-framework/anoncreds": "0.4.2",
     "@aries-framework/core": "0.4.2",
     "@aries-framework/tenants": "^0.4.2",
     "@elastic/ecs-winston-format": "^1.5.0",
diff --git a/apps/shared/src/events/didEvents.ts b/apps/shared/src/events/didEvents.ts
index 60f5c6854f6343c4e0c0832901044ed155c0a650..e300545be74213c4172a34a216ecdd7eaf50c21a 100644
--- a/apps/shared/src/events/didEvents.ts
+++ b/apps/shared/src/events/didEvents.ts
@@ -4,44 +4,36 @@ import { DidDocument, JsonTransformer } from '@aries-framework/core';
 
 import { BaseEvent } from './baseEvents.js';
 
-/**
- *
- * @todo: this should be removed as it is a weird event that should not be needed
- *
- */
-export type EventDidsPublicDidInput = BaseEventInput;
-/**
- *
- * @todo: this should be removed as it is a weird event that should not be needed
- *
- */
-export class EventDidsPublicDid extends BaseEvent<DidDocument> {
-  public static token = 'dids.publicDid';
+export type EventDidsResolveInput = BaseEventInput<{ did: string }>;
+export class EventDidsResolve extends BaseEvent<DidDocument> {
+  public static token = 'dids.resolve';
 
   public get instance() {
     return JsonTransformer.fromJSON(this.data, DidDocument);
   }
 
-  public static fromEvent(e: EventDidsPublicDid) {
-    return new EventDidsPublicDid(
-      e.data,
-      e.tenantId,
-      e.id,
-      e.type,
-      e.timestamp,
-    );
+  public static fromEvent(e: EventDidsResolve) {
+    return new EventDidsResolve(e.data, e.tenantId, e.id, e.type, e.timestamp);
   }
 }
 
-export type EventDidsResolveInput = BaseEventInput<{ did: string }>;
-export class EventDidsResolve extends BaseEvent<DidDocument> {
-  public static token = 'dids.resolve';
+export type EventDidsRegisterIndyFromSeedInput = BaseEventInput<{
+  seed: string;
+}>;
+export class EventDidsRegisterIndyFromSeed extends BaseEvent<Array<string>> {
+  public static token = 'dids.register.indy.fromSeed';
 
   public get instance() {
-    return JsonTransformer.fromJSON(this.data, DidDocument);
+    return this.data;
   }
 
-  public static fromEvent(e: EventDidsResolve) {
-    return new EventDidsResolve(e.data, e.tenantId, e.id, e.type, e.timestamp);
+  public static fromEvent(e: EventDidsRegisterIndyFromSeed) {
+    return new EventDidsRegisterIndyFromSeed(
+      e.data,
+      e.tenantId,
+      e.id,
+      e.type,
+      e.timestamp,
+    );
   }
 }
diff --git a/apps/shared/src/events/schemaEvents.ts b/apps/shared/src/events/schemaEvents.ts
new file mode 100644
index 0000000000000000000000000000000000000000..29cb53b233bd9191134ecf5606a676d784714b78
--- /dev/null
+++ b/apps/shared/src/events/schemaEvents.ts
@@ -0,0 +1,70 @@
+import type { BaseEventInput } from './baseEvents.js';
+import type { AnonCredsSchema } from '@aries-framework/anoncreds';
+
+import { BaseEvent } from './baseEvents.js';
+
+export type EventAnonCredsSchemasGetAllInput = BaseEventInput;
+export class EventAnonCredsSchemasGetAll extends BaseEvent<
+  Array<AnonCredsSchema>
+> {
+  public static token = 'anoncreds.schemas.getAll';
+
+  public get instance() {
+    return this.data;
+  }
+
+  public static fromEvent(e: EventAnonCredsSchemasGetAll) {
+    return new EventAnonCredsSchemasGetAll(
+      e.data,
+      e.tenantId,
+      e.id,
+      e.type,
+      e.timestamp,
+    );
+  }
+}
+
+export type EventAnonCredsSchemasGetByIdInput = BaseEventInput<{
+  schemaId: string;
+}>;
+export class EventAnonCredsSchemasGetById extends BaseEvent<AnonCredsSchema | null> {
+  public static token = 'anoncreds.schemas.getById';
+
+  public get instance() {
+    return this.data;
+  }
+
+  public static fromEvent(e: EventAnonCredsSchemasGetById) {
+    return new EventAnonCredsSchemasGetById(
+      e.data,
+      e.tenantId,
+      e.id,
+      e.type,
+      e.timestamp,
+    );
+  }
+}
+
+export type EventAnonCredsSchemasRegisterInput = BaseEventInput<{
+  issuerDid: string;
+  name: string;
+  version: string;
+  attributeNames: Array<string>;
+}>;
+export class EventAnonCredsSchemasRegister extends BaseEvent<AnonCredsSchema> {
+  public static token = 'anoncreds.schemas.register';
+
+  public get instance() {
+    return this.data;
+  }
+
+  public static fromEvent(e: EventAnonCredsSchemasRegister) {
+    return new EventAnonCredsSchemasRegister(
+      e.data,
+      e.tenantId,
+      e.id,
+      e.type,
+      e.timestamp,
+    );
+  }
+}
diff --git a/apps/shared/src/index.ts b/apps/shared/src/index.ts
index 42fe80034abf82559f144a6549e28bfc314b6b55..7f8ae5efe78d58642c60d2b62c1cd718d0e501e5 100644
--- a/apps/shared/src/index.ts
+++ b/apps/shared/src/index.ts
@@ -7,3 +7,4 @@ export * from './logging/logAxiosError.js';
 export * from './events/connectionEvents.js';
 export * from './events/didEvents.js';
 export * from './events/tenantEvents.js';
+export * from './events/schemaEvents.js';
diff --git a/apps/ssi-abstraction/src/agent/agent.service.ts b/apps/ssi-abstraction/src/agent/agent.service.ts
index 76b0bad51df5ce8abd267e2f08d56bf83f86de1c..9df1a7034eb1dec7eb9352566ea01366f26e7786 100644
--- a/apps/ssi-abstraction/src/agent/agent.service.ts
+++ b/apps/ssi-abstraction/src/agent/agent.service.ts
@@ -16,11 +16,9 @@ import {
   JwkDidResolver,
   KeyDidRegistrar,
   KeyDidResolver,
-  KeyType,
   LogLevel,
   PeerDidRegistrar,
   PeerDidResolver,
-  TypedArrayEncoder,
   WebDidResolver,
 } from '@aries-framework/core';
 import {
@@ -40,7 +38,6 @@ import { logger } from '@ocm/shared';
 
 import { LEDGERS } from '../config/ledger.js';
 
-import { registerPublicDids } from './ledger/register.js';
 import { AgentLogger } from './logger.js';
 
 export type AppAgent = Agent<AgentService['modules']>;
@@ -127,7 +124,7 @@ export class AgentService implements OnApplicationShutdown {
     };
   }
 
-  public get ledgers() {
+  private get ledgers() {
     const ledgerIds = this.configService.get('agent.ledgerIds');
 
     if (!ledgerIds || ledgerIds.length < 1 || ledgerIds[0] === '') {
@@ -153,41 +150,8 @@ export class AgentService implements OnApplicationShutdown {
     });
   }
 
-  private async registerPublicDid() {
-    const { publicDidSeed, ledgerIds } = this.configService.get('agent');
-
-    if (!publicDidSeed) {
-      logger.info('No public did seed provided, skipping registration');
-      return;
-    }
-
-    if (!ledgerIds || ledgerIds.length < 1 || ledgerIds[0] === '') {
-      return;
-    }
-
-    const registeredPublicDidResponses = await registerPublicDids({
-      alias: this.config.label,
-      ledgerIds,
-      seed: publicDidSeed,
-    });
-
-    for (const publicDidResponse of registeredPublicDidResponses) {
-      await this.agent.dids.import({
-        overwrite: true,
-        did: publicDidResponse.did,
-        privateKeys: [
-          {
-            keyType: KeyType.Ed25519,
-            privateKey: TypedArrayEncoder.fromString(publicDidSeed),
-          },
-        ],
-      });
-    }
-  }
-
   public async onModuleInit() {
     await this.agent.initialize();
-    await this.registerPublicDid();
     logger.info('Agent initialized');
   }
 
diff --git a/apps/ssi-abstraction/src/agent/dids/__tests__/dids.controller.spec.ts b/apps/ssi-abstraction/src/agent/dids/__tests__/dids.controller.spec.ts
index eb513d9d30947af2b8a02112ec885911fa424805..7bdb15e4d35aadc60eca85b1b1c056b094cf41d8 100644
--- a/apps/ssi-abstraction/src/agent/dids/__tests__/dids.controller.spec.ts
+++ b/apps/ssi-abstraction/src/agent/dids/__tests__/dids.controller.spec.ts
@@ -34,4 +34,20 @@ describe('DidsController', () => {
       expect(event.data).toStrictEqual(result);
     });
   });
+
+  describe('register indy did from seed', () => {
+    it('should register an indy did from seed', async () => {
+      const result = ['did:indy:bcovrin:test:mock'];
+      jest
+        .spyOn(didsService, 'registerDidIndyFromSeed')
+        .mockResolvedValue(result);
+
+      const event = await didsController.registerFromSeed({
+        seed: 'random-secure-seed',
+        tenantId: 'some-id',
+      });
+
+      expect(event.data).toStrictEqual(result);
+    });
+  });
 });
diff --git a/apps/ssi-abstraction/src/agent/dids/dids.controller.ts b/apps/ssi-abstraction/src/agent/dids/dids.controller.ts
index c869d0db7f116abed9397c663753c5d2a52a3ad0..688d68862c41553b2e9d83fc60d16aad11a741a1 100644
--- a/apps/ssi-abstraction/src/agent/dids/dids.controller.ts
+++ b/apps/ssi-abstraction/src/agent/dids/dids.controller.ts
@@ -1,8 +1,8 @@
 import { Controller } from '@nestjs/common';
 import { MessagePattern } from '@nestjs/microservices';
 import {
-  EventDidsPublicDid,
-  EventDidsPublicDidInput,
+  EventDidsRegisterIndyFromSeed,
+  EventDidsRegisterIndyFromSeedInput,
   EventDidsResolve,
   EventDidsResolveInput,
 } from '@ocm/shared';
@@ -13,10 +13,10 @@ import { DidsService } from './dids.service.js';
 export class DidsController {
   public constructor(private didsService: DidsService) {}
 
-  @MessagePattern(EventDidsPublicDid.token)
-  public async publicDid(options: EventDidsPublicDidInput) {
-    return new EventDidsPublicDid(
-      await this.didsService.getPublicDid(options),
+  @MessagePattern(EventDidsRegisterIndyFromSeed.token)
+  public async registerFromSeed(options: EventDidsRegisterIndyFromSeedInput) {
+    return new EventDidsRegisterIndyFromSeed(
+      await this.didsService.registerDidIndyFromSeed(options),
       options.tenantId,
     );
   }
diff --git a/apps/ssi-abstraction/src/agent/dids/dids.module.ts b/apps/ssi-abstraction/src/agent/dids/dids.module.ts
index 18668fe548266d2334051dc2f806eecb824060f7..d0a3f1559bf3705567da090720d4164fb3910a86 100644
--- a/apps/ssi-abstraction/src/agent/dids/dids.module.ts
+++ b/apps/ssi-abstraction/src/agent/dids/dids.module.ts
@@ -1,4 +1,5 @@
 import { Module } from '@nestjs/common';
+import { ConfigModule } from '@nestjs/config';
 
 import { AgentModule } from '../agent.module.js';
 
@@ -6,7 +7,7 @@ import { DidsController } from './dids.controller.js';
 import { DidsService } from './dids.service.js';
 
 @Module({
-  imports: [AgentModule ],
+  imports: [AgentModule, ConfigModule],
   providers: [DidsService],
   controllers: [DidsController],
 })
diff --git a/apps/ssi-abstraction/src/agent/dids/dids.service.ts b/apps/ssi-abstraction/src/agent/dids/dids.service.ts
index 2adf4c401c4c9d93e10e5f79ff1185bbd6cdf5a8..cf7807236f57ca373f86fe84a05b95dba260dda5 100644
--- a/apps/ssi-abstraction/src/agent/dids/dids.service.ts
+++ b/apps/ssi-abstraction/src/agent/dids/dids.service.ts
@@ -1,18 +1,23 @@
-import type {
-  EventDidsPublicDidInput,
-  EventDidsResolveInput,
-} from '@ocm/shared';
+import type { EventDidsResolveInput } from '@ocm/shared';
 
+import { KeyType, TypedArrayEncoder } from '@aries-framework/core';
 import { Injectable } from '@nestjs/common';
+import { ConfigService } from '@nestjs/config';
 
+import { registerPublicDids } from '../ledger/register.js';
 import { WithTenantService } from '../withTenantService.js';
 
 @Injectable()
 export class DidsService {
-  public withTenantService: WithTenantService;
+  private withTenantService: WithTenantService;
+  private configService: ConfigService;
 
-  public constructor(withTenantService: WithTenantService) {
+  public constructor(
+    withTenantService: WithTenantService,
+    configService: ConfigService,
+  ) {
     this.withTenantService = withTenantService;
+    this.configService = configService;
   }
 
   public async resolve({ did, tenantId }: EventDidsResolveInput) {
@@ -34,26 +39,35 @@ export class DidsService {
     });
   }
 
-  public async getPublicDid({ tenantId }: EventDidsPublicDidInput) {
-    return this.withTenantService.invoke(tenantId, async (t) => {
-      const dids = await t.dids.getCreatedDids({ method: 'indy' });
-      if (dids.length === 0) {
-        throw new Error('No registered public DIDs');
-      }
+  public async registerDidIndyFromSeed({
+    tenantId,
+    seed,
+  }: {
+    tenantId: string;
+    seed: string;
+  }): Promise<Array<string>> {
+    const ledgerIds = this.configService.get('agent.ledgerIds');
 
-      if (dids.length > 1) {
-        throw new Error('Multiple public DIDs found');
-      }
+    const registeredPublicDidResponses = await registerPublicDids({
+      ledgerIds,
+      seed,
+    });
 
-      const didRecord = dids[0];
+    for (const publicDidResponse of registeredPublicDidResponses) {
+      await this.withTenantService.invoke(tenantId, (t) =>
+        t.dids.import({
+          overwrite: true,
+          did: publicDidResponse.did,
+          privateKeys: [
+            {
+              keyType: KeyType.Ed25519,
+              privateKey: TypedArrayEncoder.fromString(seed),
+            },
+          ],
+        }),
+      );
+    }
 
-      if (!didRecord.didDocument) {
-        throw new Error(
-          'A public DID was found, but did not include a DID Document',
-        );
-      }
-
-      return didRecord.didDocument;
-    });
+    return registeredPublicDidResponses.map((r) => r.did);
   }
 }
diff --git a/apps/ssi-abstraction/src/agent/ledger/register.ts b/apps/ssi-abstraction/src/agent/ledger/register.ts
index 1396428d29e68d21ab7df21390bdd070fdb1336c..8600a454b48e609e2798aba6482cbd719720c419 100644
--- a/apps/ssi-abstraction/src/agent/ledger/register.ts
+++ b/apps/ssi-abstraction/src/agent/ledger/register.ts
@@ -6,14 +6,12 @@ import axios from 'axios';
 import { LEDGERS } from '../../config/ledger.js';
 
 type RegisterPublicDidOptions = {
-  alias: string;
   ledgerIds: Array<LedgerIds>;
   seed: string;
 };
 
 type LedgerRegistrationBody = {
   role?: 'ENDORSER';
-  alias?: string;
   seed: string;
 };
 
@@ -25,7 +23,6 @@ type RegisterPublicDidResponse = {
 
 export const registerPublicDids = async ({
   ledgerIds,
-  alias,
   seed,
 }: RegisterPublicDidOptions): Promise<Array<RegisterPublicDidResponse>> => {
   const responses: Array<RegisterPublicDidResponse> = [];
@@ -36,7 +33,6 @@ export const registerPublicDids = async ({
 
       const body: LedgerRegistrationBody = {
         role: 'ENDORSER',
-        alias,
         seed,
       };
 
diff --git a/apps/ssi-abstraction/src/agent/schemas/__tests__/schemas.controller.spec.ts b/apps/ssi-abstraction/src/agent/schemas/__tests__/schemas.controller.spec.ts
new file mode 100644
index 0000000000000000000000000000000000000000..d761a213358d7eb77c68c21053480efa68193867
--- /dev/null
+++ b/apps/ssi-abstraction/src/agent/schemas/__tests__/schemas.controller.spec.ts
@@ -0,0 +1,74 @@
+import type { AnonCredsSchema } from '@aries-framework/anoncreds';
+
+import { Test } from '@nestjs/testing';
+
+import { mockConfigModule } from '../../../config/__tests__/mockConfig.js';
+import { AgentModule } from '../../agent.module.js';
+import { SchemasController } from '../schemas.controller.js';
+import { SchemasService } from '../schemas.service.js';
+
+describe('ConnectionsController', () => {
+  let schemasController: SchemasController;
+  let schemasService: SchemasService;
+
+  beforeEach(async () => {
+    const moduleRef = await Test.createTestingModule({
+      imports: [mockConfigModule(), AgentModule],
+      controllers: [SchemasController],
+      providers: [SchemasService],
+    }).compile();
+
+    schemasService = moduleRef.get(SchemasService);
+    schemasController = moduleRef.get(SchemasController);
+  });
+
+  describe('get all', () => {
+    it('should get all the registered schemas of the agent', async () => {
+      const result: Array<AnonCredsSchema> = [];
+      jest.spyOn(schemasService, 'getAll').mockResolvedValue(result);
+
+      const event = await schemasController.getAll({
+        tenantId: 'some-id',
+      });
+
+      expect(event.data).toStrictEqual(result);
+    });
+  });
+
+  describe('get by id', () => {
+    it('should get a schema by id', async () => {
+      const result: AnonCredsSchema | null = null;
+      jest.spyOn(schemasService, 'getById').mockResolvedValue(result);
+
+      const event = await schemasController.getById({
+        schemaId: 'id',
+        tenantId: 'some-id',
+      });
+
+      expect(event.data).toStrictEqual(result);
+    });
+  });
+
+  describe('register schema', () => {
+    it('should register a schema on a ledger', async () => {
+      const result: AnonCredsSchema = {
+        name: 'schema-name',
+        version: '1.0',
+        issuerId: 'did:indy:123',
+        attrNames: ['name', 'age'],
+      };
+
+      jest.spyOn(schemasService, 'register').mockResolvedValue(result);
+
+      const event = await schemasController.register({
+        tenantId: 'some-id',
+        version: '1.0',
+        name: 'schema-name',
+        issuerDid: 'did:indy:123',
+        attributeNames: ['name', 'age'],
+      });
+
+      expect(event.data).toStrictEqual(result);
+    });
+  });
+});
diff --git a/apps/ssi-abstraction/src/agent/schemas/schemas.controller.ts b/apps/ssi-abstraction/src/agent/schemas/schemas.controller.ts
new file mode 100644
index 0000000000000000000000000000000000000000..180fd98b3be90fd778cef061ae2c415acbf54f7e
--- /dev/null
+++ b/apps/ssi-abstraction/src/agent/schemas/schemas.controller.ts
@@ -0,0 +1,47 @@
+import { Controller } from '@nestjs/common';
+import { MessagePattern } from '@nestjs/microservices';
+import {
+  EventAnonCredsSchemasGetAll,
+  EventAnonCredsSchemasGetAllInput,
+  EventAnonCredsSchemasGetById,
+  EventAnonCredsSchemasGetByIdInput,
+  EventAnonCredsSchemasRegister,
+  EventAnonCredsSchemasRegisterInput,
+} from '@ocm/shared';
+
+import { SchemasService } from './schemas.service.js';
+
+@Controller('schemas')
+export class SchemasController {
+  public constructor(private schemasService: SchemasService) {}
+
+  @MessagePattern(EventAnonCredsSchemasGetAll.token)
+  public async getAll(
+    options: EventAnonCredsSchemasGetAllInput,
+  ): Promise<EventAnonCredsSchemasGetAll> {
+    return new EventAnonCredsSchemasGetAll(
+      await this.schemasService.getAll(options),
+      options.tenantId,
+    );
+  }
+
+  @MessagePattern(EventAnonCredsSchemasGetById.token)
+  public async getById(
+    options: EventAnonCredsSchemasGetByIdInput,
+  ): Promise<EventAnonCredsSchemasGetById> {
+    return new EventAnonCredsSchemasGetById(
+      await this.schemasService.getById(options),
+      options.tenantId,
+    );
+  }
+
+  @MessagePattern(EventAnonCredsSchemasRegister.token)
+  public async register(
+    options: EventAnonCredsSchemasRegisterInput,
+  ): Promise<EventAnonCredsSchemasRegister> {
+    return new EventAnonCredsSchemasRegister(
+      await this.schemasService.register(options),
+      options.tenantId,
+    );
+  }
+}
diff --git a/apps/ssi-abstraction/src/agent/schemas/schemas.module.ts b/apps/ssi-abstraction/src/agent/schemas/schemas.module.ts
new file mode 100644
index 0000000000000000000000000000000000000000..b3faaad8d8902a6551cd0e3cab57313e5e925076
--- /dev/null
+++ b/apps/ssi-abstraction/src/agent/schemas/schemas.module.ts
@@ -0,0 +1,13 @@
+import { Module } from '@nestjs/common';
+
+import { AgentModule } from '../agent.module.js';
+
+import { SchemasController } from './schemas.controller.js';
+import { SchemasService } from './schemas.service.js';
+
+@Module({
+  imports: [AgentModule],
+  providers: [SchemasService],
+  controllers: [SchemasController],
+})
+export class SchemasModule {}
diff --git a/apps/ssi-abstraction/src/agent/schemas/schemas.service.ts b/apps/ssi-abstraction/src/agent/schemas/schemas.service.ts
new file mode 100644
index 0000000000000000000000000000000000000000..da3a7dc0c6f8b1e7a869d902fb0d3b8abcb10adf
--- /dev/null
+++ b/apps/ssi-abstraction/src/agent/schemas/schemas.service.ts
@@ -0,0 +1,72 @@
+import type { AnonCredsSchema } from '@aries-framework/anoncreds';
+import type { IndyVdrRegisterSchemaOptions } from '@aries-framework/indy-vdr';
+import type {
+  EventAnonCredsSchemasGetAllInput,
+  EventAnonCredsSchemasGetByIdInput,
+  EventAnonCredsSchemasRegisterInput,
+} from '@ocm/shared';
+
+import { Injectable } from '@nestjs/common';
+
+import { WithTenantService } from '../withTenantService.js';
+
+@Injectable()
+export class SchemasService {
+  public withTenantService: WithTenantService;
+
+  public constructor(withTenantService: WithTenantService) {
+    this.withTenantService = withTenantService;
+  }
+
+  public async getAll({
+    tenantId,
+  }: EventAnonCredsSchemasGetAllInput): Promise<Array<AnonCredsSchema>> {
+    return this.withTenantService.invoke(tenantId, async (t) =>
+      (await t.modules.anoncreds.getCreatedSchemas({})).map((r) => r.schema),
+    );
+  }
+
+  public async getById({
+    tenantId,
+    schemaId,
+  }: EventAnonCredsSchemasGetByIdInput): Promise<AnonCredsSchema | null> {
+    return this.withTenantService.invoke(tenantId, async (t) => {
+      const { schema } = await t.modules.anoncreds.getSchema(schemaId);
+      return schema ?? null;
+    });
+  }
+
+  public async register({
+    tenantId,
+    name,
+    version,
+    issuerDid,
+    attributeNames,
+  }: EventAnonCredsSchemasRegisterInput): Promise<AnonCredsSchema> {
+    return this.withTenantService.invoke(tenantId, async (t) => {
+      const { schemaState } =
+        await t.modules.anoncreds.registerSchema<IndyVdrRegisterSchemaOptions>({
+          schema: {
+            version,
+            name,
+            issuerId: issuerDid,
+            attrNames: attributeNames,
+          },
+          options: {
+            endorserMode: 'external',
+            endorserDid: issuerDid,
+          },
+        });
+
+      if (schemaState.state !== 'finished' && schemaState.state !== 'action') {
+        throw new Error(
+          `Error registering schema: ${
+            schemaState.state === 'failed' ? schemaState.reason : 'Not Finished'
+          }`,
+        );
+      }
+
+      return schemaState.schema;
+    });
+  }
+}
diff --git a/apps/ssi-abstraction/src/app.module.ts b/apps/ssi-abstraction/src/app.module.ts
index 44a25ae0403ab4bd45ec9a8663f09231f491ff2c..52662f0ae58d187ef48317b71e3b77d7db74ef30 100644
--- a/apps/ssi-abstraction/src/app.module.ts
+++ b/apps/ssi-abstraction/src/app.module.ts
@@ -6,6 +6,7 @@ import { HealthController } from '@ocm/shared';
 
 import { AgentModule } from './agent/agent.module.js';
 import { ConnectionsModule } from './agent/connections/connections.module.js';
+import { SchemasModule } from './agent/schemas/schemas.module.js';
 import { TenantsModule } from './agent/tenants/tenants.module.js';
 import { config } from './config/config.js';
 import { validationSchema } from './config/validation.js';
@@ -21,6 +22,7 @@ import { validationSchema } from './config/validation.js';
     AgentModule,
     ConnectionsModule,
     DidsModule,
+    SchemasModule,
     TenantsModule,
   ],
   controllers: [HealthController],
diff --git a/apps/ssi-abstraction/src/config/__tests__/mockConfig.ts b/apps/ssi-abstraction/src/config/__tests__/mockConfig.ts
index 8a5eb2ed2efb4ddbcbea7f3d4975a60a29a83af7..202a2b05ede19e8d39aa47dc5790dcb89157c001 100644
--- a/apps/ssi-abstraction/src/config/__tests__/mockConfig.ts
+++ b/apps/ssi-abstraction/src/config/__tests__/mockConfig.ts
@@ -5,7 +5,7 @@ import { ConfigModule, ConfigService } from '@nestjs/config';
 
 import { validationSchema } from '../validation.js';
 
-const mockConfig = (port: number = 3001): AppConfig => ({
+const mockConfig = (port: number = 3001, withLedger = false): AppConfig => ({
   agentHost: '',
   port: 3000,
   jwtSecret: '',
@@ -16,19 +16,19 @@ const mockConfig = (port: number = 3001): AppConfig => ({
     name: 'my-test-agent',
     walletId: utils.uuid(),
     walletKey: 'some-key',
-    ledgerIds: [],
+    ledgerIds: withLedger ? ['BCOVRIN_TEST'] : [],
     host: 'http://localhost',
     inboundPort: port,
     path: '',
-    publicDidSeed: '',
+    publicDidSeed: withLedger ? '12312367897123300000000000000000' : '',
     autoAcceptConnection: true,
     autoAcceptCredential: AutoAcceptCredential.ContentApproved,
   },
 });
 
-export const mockConfigModule = (port: number = 3000) =>
+export const mockConfigModule = (port: number = 3000, withLedger = false) =>
   ConfigModule.forRoot({
-    load: [() => mockConfig(port)],
+    load: [() => mockConfig(port, withLedger)],
     validationSchema,
   });
 
diff --git a/apps/ssi-abstraction/test/dids.e2e-spec.ts b/apps/ssi-abstraction/test/dids.e2e-spec.ts
index 8f3bafe813ab0b21b5c7ce63ab71df7e7da923ca..52c8a7b66caaba8d59e7d2c64d98ce725e977532 100644
--- a/apps/ssi-abstraction/test/dids.e2e-spec.ts
+++ b/apps/ssi-abstraction/test/dids.e2e-spec.ts
@@ -1,37 +1,21 @@
 import type { INestApplication } from '@nestjs/common';
 import type { ClientProxy } from '@nestjs/microservices';
 import type {
+  EventDidsRegisterIndyFromSeedInput,
   EventDidsResolveInput,
-  EventDidsPublicDidInput,
 } from '@ocm/shared';
 
-import { DidDocument } from '@aries-framework/core';
 import { ClientsModule, Transport } from '@nestjs/microservices';
 import { Test } from '@nestjs/testing';
-import { EventDidsResolve, EventDidsPublicDid } from '@ocm/shared';
+import { EventDidsRegisterIndyFromSeed, EventDidsResolve } from '@ocm/shared';
 import { firstValueFrom } from 'rxjs';
 
 import { AgentModule } from '../src/agent/agent.module.js';
 import { DidsModule } from '../src/agent/dids/dids.module.js';
-import { DidsService } from '../src/agent/dids/dids.service.js';
 import { TenantsModule } from '../src/agent/tenants/tenants.module.js';
 import { TenantsService } from '../src/agent/tenants/tenants.service.js';
 import { mockConfigModule } from '../src/config/__tests__/mockConfig.js';
 
-const mockDidDocument = {
-  context: ['https://w3id.org/did/v1'],
-  id: 'did:indy:bcovrin:test:7KuDTpQh3GJ7Gp6kErpWvM',
-  verificationMethod: [
-    {
-      id: 'did:indy:bcovrin:test:7KuDTpQh3GJ7Gp6kErpWvM#verkey',
-      type: 'Ed25519VerificationKey2018',
-      controller: 'did:indy:bcovrin:test:7KuDTpQh3GJ7Gp6kErpWvM',
-      publicKeyBase58: '4SySYXQUtuK26zfC7RpQpWYMThfbXphUf8LWyXXmxyTX',
-    },
-  ],
-  authentication: ['did:indy:bcovrin:test:7KuDTpQh3GJ7Gp6kErpWvM#verkey'],
-};
-
 describe('Dids', () => {
   const TOKEN = 'DIDS_CLIENT_SERVICE';
   let app: INestApplication;
@@ -39,12 +23,9 @@ describe('Dids', () => {
   let tenantId: string;
 
   beforeAll(async () => {
-    jest
-      .spyOn(DidsService.prototype, 'getPublicDid')
-      .mockResolvedValue(new DidDocument(mockDidDocument));
     const moduleRef = await Test.createTestingModule({
       imports: [
-        mockConfigModule(3005),
+        mockConfigModule(3005, true),
         AgentModule,
         DidsModule,
         TenantsModule,
@@ -72,16 +53,21 @@ describe('Dids', () => {
     client.close();
   });
 
-  it(EventDidsPublicDid.token, async () => {
-    const response$ = client.send<EventDidsPublicDid, EventDidsPublicDidInput>(
-      EventDidsPublicDid.token,
-      { tenantId },
-    );
+  it(EventDidsRegisterIndyFromSeed.token, async () => {
+    const response$ = client.send<
+      EventDidsRegisterIndyFromSeed,
+      EventDidsRegisterIndyFromSeedInput
+    >(EventDidsRegisterIndyFromSeed.token, {
+      seed: '12312367897123300000000000000000',
+      tenantId,
+    });
 
     const response = await firstValueFrom(response$);
-    const eventInstance = EventDidsPublicDid.fromEvent(response);
+    const eventInstance = EventDidsRegisterIndyFromSeed.fromEvent(response);
 
-    expect(eventInstance.instance).toMatchObject(mockDidDocument);
+    expect(eventInstance.instance).toMatchObject(
+      expect.arrayContaining(['did:indy:bcovrin:test:9MMeff63VnCpogD2FWfKnJ']),
+    );
   });
 
   it(EventDidsResolve.token, async () => {
diff --git a/apps/ssi-abstraction/test/schemas.e2e-spec.ts b/apps/ssi-abstraction/test/schemas.e2e-spec.ts
new file mode 100644
index 0000000000000000000000000000000000000000..acfe669b6abbf07bec1d6cc50546991d29e353c1
--- /dev/null
+++ b/apps/ssi-abstraction/test/schemas.e2e-spec.ts
@@ -0,0 +1,119 @@
+import type { INestApplication } from '@nestjs/common';
+import type { ClientProxy } from '@nestjs/microservices';
+import type {
+  EventAnonCredsSchemasGetAllInput,
+  EventAnonCredsSchemasGetByIdInput,
+  EventAnonCredsSchemasRegisterInput,
+} from '@ocm/shared';
+
+import { ClientsModule, Transport } from '@nestjs/microservices';
+import { Test } from '@nestjs/testing';
+import {
+  EventAnonCredsSchemasGetAll,
+  EventAnonCredsSchemasGetById,
+  EventAnonCredsSchemasRegister,
+} from '@ocm/shared';
+import { firstValueFrom } from 'rxjs';
+
+import { AgentModule } from '../src/agent/agent.module.js';
+import { DidsModule } from '../src/agent/dids/dids.module.js';
+import { DidsService } from '../src/agent/dids/dids.service.js';
+import { SchemasModule } from '../src/agent/schemas/schemas.module.js';
+import { TenantsModule } from '../src/agent/tenants/tenants.module.js';
+import { TenantsService } from '../src/agent/tenants/tenants.service.js';
+import { mockConfigModule } from '../src/config/__tests__/mockConfig.js';
+
+describe('Schemas', () => {
+  const TOKEN = 'SCHEMAS_CLIENT_SERVICE';
+  let app: INestApplication;
+  let client: ClientProxy;
+  let tenantId: string;
+  let issuerDid: string;
+
+  beforeAll(async () => {
+    const moduleRef = await Test.createTestingModule({
+      imports: [
+        mockConfigModule(3004, true),
+        AgentModule,
+        SchemasModule,
+        TenantsModule,
+        DidsModule,
+        ClientsModule.register([{ name: TOKEN, transport: Transport.NATS }]),
+      ],
+    }).compile();
+
+    app = moduleRef.createNestApplication();
+
+    app.connectMicroservice({ transport: Transport.NATS });
+
+    await app.startAllMicroservices();
+    await app.init();
+
+    client = app.get(TOKEN);
+    await client.connect();
+
+    const ts = app.get(TenantsService);
+    const { id } = await ts.create(TOKEN);
+    tenantId = id;
+
+    const ds = app.get(DidsService);
+    const [did] = await ds.registerDidIndyFromSeed({
+      tenantId,
+      seed: '12312367897123300000000000000000',
+    });
+    issuerDid = did;
+  });
+
+  afterAll(async () => {
+    await app.close();
+    client.close();
+  });
+
+  it(EventAnonCredsSchemasGetAll.token, async () => {
+    const response$ = client.send<
+      EventAnonCredsSchemasGetAll,
+      EventAnonCredsSchemasGetAllInput
+    >(EventAnonCredsSchemasGetAll.token, { tenantId });
+    const response = await firstValueFrom(response$);
+    const eventInstance = EventAnonCredsSchemasGetAll.fromEvent(response);
+
+    expect(eventInstance.instance).toEqual(expect.arrayContaining([]));
+  });
+
+  it(EventAnonCredsSchemasGetById.token, async () => {
+    const response$ = client.send<
+      EventAnonCredsSchemasGetById,
+      EventAnonCredsSchemasGetByIdInput
+    >(EventAnonCredsSchemasGetById.token, { tenantId, schemaId: 'some-id' });
+    const response = await firstValueFrom(response$);
+    const eventInstance = EventAnonCredsSchemasGetById.fromEvent(response);
+
+    expect(eventInstance.instance).toEqual(null);
+  });
+
+  it(EventAnonCredsSchemasRegister.token, async () => {
+    const version = new Date().getTime().toString();
+    const attributeNames = ['names', 'age'];
+    const name = 'my-schema';
+    const response$ = client.send<
+      EventAnonCredsSchemasRegister,
+      EventAnonCredsSchemasRegisterInput
+    >(EventAnonCredsSchemasRegister.token, {
+      tenantId,
+      name,
+      version,
+      issuerDid,
+      attributeNames,
+    });
+
+    const response = await firstValueFrom(response$);
+    const eventInstance = EventAnonCredsSchemasRegister.fromEvent(response);
+
+    expect(eventInstance.instance).toMatchObject({
+      attrNames: attributeNames,
+      issuerId: issuerDid,
+      name,
+      version,
+    });
+  });
+});
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index 93ebdf1a54006abba74a71cf365a0f0661a32edf..0829affa99021eff4ffd7d9c6979028c77224ae7 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -682,6 +682,9 @@ importers:
 
   apps/shared:
     dependencies:
+      '@aries-framework/anoncreds':
+        specifier: 0.4.2
+        version: 0.4.2(expo@49.0.18)(react-native@0.72.7)
       '@aries-framework/core':
         specifier: 0.4.2
         version: 0.4.2(expo@49.0.18)(react-native@0.72.7)