Skip to content
Snippets Groups Projects
Unverified Commit 86ae62f7 authored by Berend Sliedrecht's avatar Berend Sliedrecht Committed by GitHub
Browse files

Merge pull request #8 from spherity/nats-communication

parents 4c79151a 7a7c64c2
No related branches found
No related tags found
No related merge requests found
Showing
with 206 additions and 192 deletions
......@@ -17,7 +17,7 @@ node_modules
# compiled output
node_modules/
apps/*/node_modules
/dist
dist
/apps/**/dist/
*.tsbuildinfo
......
{
"singleQuote": true,
"trailingComma": "all"
}
\ No newline at end of file
}
<hr/>
# Event types published on nats
* ```
{
- ```
{
endpoint: 'SSI_ABSTRACTION_SERVICE/BasicMessageStateChanged',
}
```
* ```
- ```
{
endpoint: 'SSI_ABSTRACTION_SERVICE/ConnectionStateChanged',
}
```
* ```
- ```
{
endpoint: 'SSI_ABSTRACTION_SERVICE/CredentialStateChanged',
}
```
* ```
- ```
{
endpoint: 'SSI_ABSTRACTION_SERVICE/ProofStateChanged',
}
```
* ```
- ```
{
endpoint: 'SSI_ABSTRACTION_SERVICE/MediationStateChanged',
}
```
* ```
- ```
{
endpoint: 'SSI_ABSTRACTION_SERVICE/RecipientKeylistUpdated',
}
```
* ```
- ```
{
endpoint: 'SSI_ABSTRACTION_SERVICE/OutboundWebSocketClosedEvent',
}
```
\ No newline at end of file
```
# GDPR Compliance Document
The objective of this document is to detail, the data being stored and proccessed by the Organization Credential Manager's, SSI Abstraction Services.
## What information is stored
### Source User Information
- Verifiable Credential Specific Information - The various VC's issued by the particular OCM.
- Verifiable Credential Specific Information - The various VC's issued by the particular OCM.
- Proof Presentation Specific Information - Credential Claims.
### Technical User Information (Public)
- Connection Information - The list of connections with different PCM and OCM agents and Pairwise DID.
- Schema information (public)
- Credential/credential definition ids and states
......@@ -15,14 +19,19 @@ The objective of this document is to detail, the data being stored and proccesse
- Created/updated dates
## How is the information stored
### Source User Information
User specific Source User Information is encrypted using the Private Key of the Organizations SSI Agent and stored until the issuance of credential in Organization's SSI Agent's PostgreSQL database.
### Technical User Information (Public)
Technical User Information is encrypted using the Private Key of the Organizations SSI Agent and stored internally (on the agent) on PostgreSQL.
## Who can access the information
The Source User Information and Technical User Information both are accessible only by the Organization specific SSI agent's private key.
## How long will the information stay
## How long will the information stay
The Source User Information and Technical User Information is never wiped out unless the Agent Database is cleared.
# SSI Abstraction Service
## Description
<hr/>
<p align="center">A core service for the Organizational Credential Manager, providing the DIDComm functionality and initializing the agent, wallet and ledger interactions of the whole application.</p>
## Usage
<hr/>
### Endpoint documentation at:
### Endpoint documentation at:
[Aries REST Extension](swagger.json)
......@@ -15,55 +17,59 @@
[Sign and Verify Interface](SIGN-AND-VERIFY.md)
with the default exposed ports:
* 3010 - Aries REST extension
* 3009 - Sign and Veify interface exposed
* 4000 - didcomm interface
with the default exposed ports:
- 3010 - Aries REST extension
- 3009 - Sign and Veify interface exposed
- 4000 - didcomm interface
## Installation
<hr/>
Dependencies:
```bash
$ pnpm install
$ pnpm install
```
* **If docker is not installed, [Install docker](https://docs.docker.com/engine/install/)**.
- **If docker is not installed, [Install docker](https://docs.docker.com/engine/install/)**.
* **If docker-compose is not installed, [Install docker-compose](https://docs.docker.com/compose/install/)**.
- **If docker-compose is not installed, [Install docker-compose](https://docs.docker.com/compose/install/)**.
* (optional) Postgres GUI
https://dbeaver.io/download/
- (optional) Postgres GUI
https://dbeaver.io/download/
<hr/>
## Running the app
<hr/>
### Environment variables
[.env.example](.env.example)
* PORT is the port for the signing and verification interface
* AFJ_EXT_PORT is the port for the openapi documentation described in [swagger.json](swagger.json)
* AGENT_AUTO_ACCEPT_CONNECTION can be either true or false
* AGENT_AUTO_ACCEPT_CREDENTIAL can be either: always, contentApproved, never
* AGENT_PUBLIC_DID_SEED will generate the did and verkey (32 symbols)
* for security reasons AGENT_WALLET_KEY and AGENT_WALLET_ID should be different
* AGENT_LEDGER_ID can be: ID_UNION,BCOVRIN_TEST,GREEN_LIGHT
- PORT is the port for the signing and verification interface
- AFJ_EXT_PORT is the port for the openapi documentation described in [swagger.json](swagger.json)
- AGENT_AUTO_ACCEPT_CONNECTION can be either true or false
- AGENT_AUTO_ACCEPT_CREDENTIAL can be either: always, contentApproved, never
- AGENT_PUBLIC_DID_SEED will generate the did and verkey (32 symbols)
- for security reasons AGENT_WALLET_KEY and AGENT_WALLET_ID should be different
- AGENT_LEDGER_ID can be: ID_UNION,BCOVRIN_TEST,GREEN_LIGHT
- the three pool transaction genesis are inside the code configuration
- every ledger can be provided on its own
- multiple ledgers can also be specified, separated by a comma
* AGENT_ID_UNION_KEY is needed if the ledger of choice is IDUnion
- AGENT_ID_UNION_KEY is needed if the ledger of choice is IDUnion
**Each service in the Organizational Credential Manager can be run from the infrastructure repository with Docker.**
**The .env files are in the infrastructure repository under /env**
### There are two separate Dockefiles in "./deployment" of every project:
```bash
## production in:
./deployment/ci
......@@ -71,16 +77,16 @@ https://dbeaver.io/download/
./deployment/dev
```
- (optional) Edit docker-compose.yml in "infrastructure" to use either **/ci/** or **/dev/** Dockerfiles.
* (optional) Edit docker-compose.yml in "infrastructure" to use either **/ci/** or **/dev/** Dockerfiles.
- Run while in **"infrastructure"** project:
* Run while in **"infrastructure"** project:
```bash
$ docker-compose up --build
```
## Test
<hr/>
```bash
......@@ -94,19 +100,20 @@ $ pnpm test:e2e
$ pnpm test:cov
```
## GDPR
<hr/>
[GDPR](GDPR.md)
## Dependencies
<hr/>
[Dependencies](package.json)
## License
<hr/>
[Apache 2.0 license](LICENSE)
## Signing and verification interface is accessible on SSI Abstraction
### METHOD: POST
**type: "buffer" is necessary to know internally what transformation needs to be done**
```
:3009/v1/agent/wallet/sign
......@@ -18,7 +19,9 @@ body : {
```
### Returns
```
{
statusCode: Number,
......@@ -31,18 +34,18 @@ body : {
and
<hr/>
### METHOD: POST
```
:3009/v1/agent/wallet/verify
body : {
data: [
signerVerkey: string,
signerVerkey: string,
{
type: "buffer",
dataBase64: base64 string //// This is the data to be verified
},
},
{
type: "buffer",
dataBase64: base64 string //// This is the signature
......@@ -50,7 +53,9 @@ body : {
]
}
```
### Returns
```
{
statusCode: Number,
......@@ -59,10 +64,6 @@ body : {
}
```
## Get Agent Info endpoint (did, verkey) on SSI Abstraction
### METHOD: GET
......@@ -72,6 +73,7 @@ body : {
```
### Returns
```
{
"statusCode": 200,
......@@ -81,4 +83,4 @@ body : {
"verkey": string // verkey needed for signing and verification
}
}
```
\ No newline at end of file
```
......@@ -3,4 +3,4 @@ appVersion: v1.0.3-rc
description: ssi-abstraction deployment
name: ssi-abstraction
version: 1.0.3
icon: "https://www.vereign.com/wp-content/themes/vereign2020/images/vereign-logo.svg"
icon: 'https://www.vereign.com/wp-content/themes/vereign2020/images/vereign-logo.svg'
......@@ -6,68 +6,69 @@ ssi-abstraction deployment
## Values
| Key | Type | Default | Description |
|-----|------|---------|-------------|
| autoscaling.enabled | bool | `false` | Enable autoscaling |
| autoscaling.maxReplicas | int | `3` | Maximum replicas |
| autoscaling.minReplicas | int | `1` | Minimum replicas |
| autoscaling.targetCPUUtilizationPercentage | int | `70` | CPU target for autoscaling trigger |
| autoscaling.targetMemoryUtilizationPercentage | int | `70` | Memory target for autoscaling trigger |
| image.name | string | `"gaiax/ssi-abstraction"` | Image name |
| image.pullPolicy | string | `"IfNotPresent"` | Image pull policy |
| image.pullSecrets | string | `"deployment-key-light"` | Image pull secret when internal image is used |
| image.repository | string | `"eu.gcr.io/vrgn-infra-prj"` | |
| image.sha | string | `""` | Image sha, usually generated by the CI Uses image.tag if empty |
| image.tag | string | `""` | Image tag Uses .Chart.AppVersion if empty |
| ingress.annotations."cert-manager.io/cluster-issuer" | string | `"letsencrypt-production-http"` | |
| ingress.annotations."kubernetes.io/ingress.class" | string | `"nginx"` | |
| ingress.annotations."kubernetes.io/ingress.global-static-ip-name" | string | `"dev-light-public"` | |
| ingress.annotations."nginx.ingress.kubernetes.io/rewrite-target" | string | `"/$2"` | |
| ingress.enabled | bool | `true` | |
| ingress.frontendDomain | string | `"gaiax.vereign.com"` | |
| ingress.frontendTlsSecretName | string | `"cert-manager-tls"` | |
| ingress.pathOverride | string | `"didcomm"` | |
| ingress.tlsEnabled | bool | `true` | |
| log.encoding | string | `"json"` | |
| log.level | string | `"INFO"` | |
| metrics.enabled | bool | `true` | Enable prometheus metrics |
| metrics.port | int | `2112` | Port for prometheus metrics |
| name | string | `"ssi-abstraction"` | Application name |
| nameOverride | string | `""` | Ovverwrites application name |
| podAnnotations | object | `{}` | |
| replicaCount | int | `1` | Default number of instances to start |
| resources.limits.cpu | string | `"150m"` | |
| resources.limits.memory | string | `"512Mi"` | |
| resources.requests.cpu | string | `"25m"` | |
| resources.requests.memory | string | `"64Mi"` | |
| security.runAsGid | int | `0` | Group used by the apps |
| security.runAsNonRoot | bool | `false` | by default, apps run as non-root |
| security.runAsUid | int | `0` | User used by the apps |
| service.port | int | `3009` | |
| ssiAbstraction.afjExtPort | int | `3010` | |
| ssiAbstraction.agent.autoAccept.connection | bool | `true` | |
| ssiAbstraction.agent.autoAccept.credential | bool | `true` | |
| ssiAbstraction.agent.host | string | `"gaiax.vereign.com"` | |
| ssiAbstraction.agent.ledgerId | string | `"ID_UNION"` | |
| ssiAbstraction.agent.name | string | `"ssi-abstraction-agent"` | |
| ssiAbstraction.agent.peerPort | int | `443` | |
| ssiAbstraction.agent.protocol | string | `"http"` | |
| ssiAbstraction.agent.publicDidSeed | string | `"6b8b882e2618fa5d45ee7229ca880083"` | |
| ssiAbstraction.agent.urlPath | string | `"/ocm/didcomm"` | |
| ssiAbstraction.agent.wallet.id | string | `"ssi-wallet-id"` | |
| ssiAbstraction.agent.wallet.key | string | `"ssi-wallet-key"` | |
| ssiAbstraction.database.db | string | `"postgres"` | |
| ssiAbstraction.database.host | string | `"postgresql.infra"` | |
| ssiAbstraction.database.password | string | `"password"` | |
| ssiAbstraction.database.port | int | `5432` | |
| ssiAbstraction.database.schema | string | `"proof"` | |
| ssiAbstraction.database.user | string | `"root"` | |
| ssiAbstraction.elastic.port | int | `9200` | |
| ssiAbstraction.elastic.protocol | string | `"http"` | |
| ssiAbstraction.elastic.url | string | `"elasticsearch"` | |
| ssiAbstraction.nats.port | int | `4222` | |
| ssiAbstraction.nats.protocol | string | `"nats"` | |
| ssiAbstraction.nats.url | string | `"nats"` | |
| Key | Type | Default | Description |
| ----------------------------------------------------------------- | ------ | ------------------------------------ | -------------------------------------------------------------- |
| autoscaling.enabled | bool | `false` | Enable autoscaling |
| autoscaling.maxReplicas | int | `3` | Maximum replicas |
| autoscaling.minReplicas | int | `1` | Minimum replicas |
| autoscaling.targetCPUUtilizationPercentage | int | `70` | CPU target for autoscaling trigger |
| autoscaling.targetMemoryUtilizationPercentage | int | `70` | Memory target for autoscaling trigger |
| image.name | string | `"gaiax/ssi-abstraction"` | Image name |
| image.pullPolicy | string | `"IfNotPresent"` | Image pull policy |
| image.pullSecrets | string | `"deployment-key-light"` | Image pull secret when internal image is used |
| image.repository | string | `"eu.gcr.io/vrgn-infra-prj"` | |
| image.sha | string | `""` | Image sha, usually generated by the CI Uses image.tag if empty |
| image.tag | string | `""` | Image tag Uses .Chart.AppVersion if empty |
| ingress.annotations."cert-manager.io/cluster-issuer" | string | `"letsencrypt-production-http"` | |
| ingress.annotations."kubernetes.io/ingress.class" | string | `"nginx"` | |
| ingress.annotations."kubernetes.io/ingress.global-static-ip-name" | string | `"dev-light-public"` | |
| ingress.annotations."nginx.ingress.kubernetes.io/rewrite-target" | string | `"/$2"` | |
| ingress.enabled | bool | `true` | |
| ingress.frontendDomain | string | `"gaiax.vereign.com"` | |
| ingress.frontendTlsSecretName | string | `"cert-manager-tls"` | |
| ingress.pathOverride | string | `"didcomm"` | |
| ingress.tlsEnabled | bool | `true` | |
| log.encoding | string | `"json"` | |
| log.level | string | `"INFO"` | |
| metrics.enabled | bool | `true` | Enable prometheus metrics |
| metrics.port | int | `2112` | Port for prometheus metrics |
| name | string | `"ssi-abstraction"` | Application name |
| nameOverride | string | `""` | Ovverwrites application name |
| podAnnotations | object | `{}` | |
| replicaCount | int | `1` | Default number of instances to start |
| resources.limits.cpu | string | `"150m"` | |
| resources.limits.memory | string | `"512Mi"` | |
| resources.requests.cpu | string | `"25m"` | |
| resources.requests.memory | string | `"64Mi"` | |
| security.runAsGid | int | `0` | Group used by the apps |
| security.runAsNonRoot | bool | `false` | by default, apps run as non-root |
| security.runAsUid | int | `0` | User used by the apps |
| service.port | int | `3009` | |
| ssiAbstraction.afjExtPort | int | `3010` | |
| ssiAbstraction.agent.autoAccept.connection | bool | `true` | |
| ssiAbstraction.agent.autoAccept.credential | bool | `true` | |
| ssiAbstraction.agent.host | string | `"gaiax.vereign.com"` | |
| ssiAbstraction.agent.ledgerId | string | `"ID_UNION"` | |
| ssiAbstraction.agent.name | string | `"ssi-abstraction-agent"` | |
| ssiAbstraction.agent.peerPort | int | `443` | |
| ssiAbstraction.agent.protocol | string | `"http"` | |
| ssiAbstraction.agent.publicDidSeed | string | `"6b8b882e2618fa5d45ee7229ca880083"` | |
| ssiAbstraction.agent.urlPath | string | `"/ocm/didcomm"` | |
| ssiAbstraction.agent.wallet.id | string | `"ssi-wallet-id"` | |
| ssiAbstraction.agent.wallet.key | string | `"ssi-wallet-key"` | |
| ssiAbstraction.database.db | string | `"postgres"` | |
| ssiAbstraction.database.host | string | `"postgresql.infra"` | |
| ssiAbstraction.database.password | string | `"password"` | |
| ssiAbstraction.database.port | int | `5432` | |
| ssiAbstraction.database.schema | string | `"proof"` | |
| ssiAbstraction.database.user | string | `"root"` | |
| ssiAbstraction.elastic.port | int | `9200` | |
| ssiAbstraction.elastic.protocol | string | `"http"` | |
| ssiAbstraction.elastic.url | string | `"elasticsearch"` | |
| ssiAbstraction.nats.port | int | `4222` | |
| ssiAbstraction.nats.protocol | string | `"nats"` | |
| ssiAbstraction.nats.url | string | `"nats"` | |
---
----------------------------------------------
Autogenerated from chart metadata using [helm-docs v1.10.0](https://github.com/norwoodj/helm-docs/releases/v1.10.0)
apiVersion: v1
kind: Service
metadata:
name: {{ template "app.name" . }}
namespace: {{ .Release.Namespace }}
labels:
{{- include "app.labels" . | nindent 4 }}
name: { { template "app.name" . } }
namespace: { { .Release.Namespace } }
labels: { { - include "app.labels" . | nindent 4 } }
spec:
clusterIP: None
ports:
- name: http
port: {{ .Values.service.port }}
targetPort: {{ .Values.service.port }}
- name: afj
port: {{ .Values.ssiAbstraction.afjExtPort }}
targetPort: {{ .Values.ssiAbstraction.afjExtPort }}
- name: peer
port: {{ .Values.ssiAbstraction.agent.peerPort }}
targetPort: {{ .Values.ssiAbstraction.agent.peerPort }}
selector:
{{- include "app.selectorLabels" . | nindent 4 }}
- name: http
port: { { .Values.service.port } }
targetPort: { { .Values.service.port } }
- name: afj
port: { { .Values.ssiAbstraction.afjExtPort } }
targetPort: { { .Values.ssiAbstraction.afjExtPort } }
- name: peer
port: { { .Values.ssiAbstraction.agent.peerPort } }
targetPort: { { .Values.ssiAbstraction.agent.peerPort } }
selector: { { - include "app.selectorLabels" . | nindent 4 } }
# -- Default number of instances to start
# -- Default number of instances to start
replicaCount: 1
# -- Application name
name: ssi-abstraction
# -- Ovverwrites application name
nameOverride: ""
nameOverride: ''
image:
repository: eu.gcr.io/vrgn-infra-prj
......@@ -11,16 +11,15 @@ image:
name: gaiax/ssi-abstraction
# -- Image tag
# Uses .Chart.AppVersion if empty
tag: ""
tag: ''
# -- Image sha, usually generated by the CI
# Uses image.tag if empty
sha: ""
sha: ''
# -- Image pull policy
pullPolicy: IfNotPresent
# -- Image pull secret when internal image is used
pullSecrets: deployment-key-light
podAnnotations: {}
##
## Pass extra environment variables to the container.
......@@ -67,7 +66,7 @@ metrics:
port: 2112
log:
level: "INFO"
level: 'INFO'
encoding: json
##
......@@ -127,4 +126,4 @@ ingress:
tlsEnabled: true
frontendDomain: gaiax.vereign.com
frontendTlsSecretName: cert-manager-tls
pathOverride: didcomm
\ No newline at end of file
pathOverride: didcomm
......@@ -19,8 +19,6 @@
"start:docker": "pnpm prisma:generate && pnpm dbSchema && pnpm start",
"lint": "eslint .",
"lint:fix": "pnpm lint --fix",
"format": "prettier --write",
"format:all": "npm run format -- .",
"test": "jest",
"test:watch": "jest --watch",
"test:cov": "jest --coverage",
......
import { Controller, Get, HttpStatus } from '@nestjs/common';
import { Controller } from '@nestjs/common';
import { MessagePattern } from '@nestjs/microservices';
import { AgentService } from './agent.service.js';
@Controller('agent')
export class AgentController {
public constructor(private agent: AgentService) {}
@Get('info')
async getWalletInfo() {
@MessagePattern('info.publicDid')
async publicDid() {
return {
statusCode: HttpStatus.OK,
message: 'Success',
data: 'SHOULD_BE_PUBLIC_DID',
id: 'test',
};
}
}
export default AgentController;
import { Module } from '@nestjs/common';
import { ConfigModule } from '@nestjs/config';
import { ClientsModule, Transport } from '@nestjs/microservices';
import config from '../config/config.js';
import { NatsClientService } from '../client/nats.client.js';
import { NATSServices } from '../common/constants.js';
import { AgentController } from './agent.controller.js';
import { AgentService } from './agent.service.js';
@Module({
imports: [
ConfigModule,
ClientsModule.register([
{
name: NATSServices.SERVICE_NAME,
transport: Transport.NATS,
options: {
servers: [config().nats.url],
},
},
]),
],
providers: [NatsClientService, AgentService],
imports: [ConfigModule],
providers: [AgentService],
controllers: [AgentController],
exports: [AgentService],
})
export class AgentModule {}
export default AgentModule;
......@@ -29,7 +29,6 @@ import {
IndyVdrPoolConfig,
IndyVdrSovDidResolver,
} from '@aries-framework/indy-vdr';
import { subscribe } from './utils/listener.js';
import {
LedgerIds,
ledgerNamespaces,
......@@ -37,23 +36,18 @@ import {
} from './utils/ledgerConfig.js';
import { AgentLogger } from './utils/logger.js';
import { registerPublicDids } from './ledger/register.js';
import { NatsClientService } from '../client/nats.client.js';
import logger from '../globalUtils/logger.js';
import { logger } from '../globalUtils/logger.js';
export type AppAgent = Agent<AgentService['modules']>;
@Injectable()
export class AgentService {
private agent: Agent<this['modules']>;
public agent: AppAgent;
private configService: ConfigService;
private natsClient: NatsClientService;
public constructor(
configService: ConfigService,
natsClient: NatsClientService,
) {
public constructor(configService: ConfigService) {
this.configService = configService;
this.natsClient = natsClient;
const peerPort = this.configService.get('agent.peerPort');
......@@ -185,7 +179,6 @@ export class AgentService {
public async onModuleInit() {
await this.agent.initialize();
await this.registerPublicDid();
subscribe(this.agent, this.natsClient);
logger.info('Agent initialized');
}
......@@ -193,5 +186,3 @@ export class AgentService {
await this.agent.shutdown();
}
}
export default AgentService;
import { Controller } from '@nestjs/common';
import { MessagePattern } from '@nestjs/microservices';
import { ConnectionService } from './connection.service.js';
@Controller('connection')
export class ConnectionController {
public constructor(private connectionService: ConnectionService) {}
@MessagePattern('connection.getAll')
async getAll() {
return await this.connectionService.getAll();
}
}
import { Module } from '@nestjs/common';
import { AgentModule } from '../agent.module.js';
import { ConnectionController } from './connection.controller.js';
import { ConnectionService } from './connection.service.js';
@Module({
imports: [AgentModule],
providers: [ConnectionService],
controllers: [ConnectionController],
})
export class ConnectionModule {}
import { ConnectionRecord } from '@aries-framework/core';
import { Injectable } from '@nestjs/common';
import { AgentService, AppAgent } from '../agent.service.js';
@Injectable()
export class ConnectionService {
public agent: AppAgent;
public constructor(agentService: AgentService) {
this.agent = agentService.agent;
}
public async getAll(): Promise<Array<ConnectionRecord>> {
return await this.agent.connections.getAll();
}
}
import logger from '../../globalUtils/logger.js';
import { logger } from '../../globalUtils/logger.js';
import axios from 'axios';
import { logAxiosError } from '../utils/helperFunctions.js';
import { LedgerIds, ledgerNamespaces, NYM_URL } from '../utils/ledgerConfig.js';
......@@ -52,10 +52,8 @@ export const registerPublicDids = async ({
}
} catch (err) {
// if did is already registered on IdUnion it will catch 500, but it's ok
logAxiosError(err);
if (err instanceof axios.AxiosError) logAxiosError(err);
}
}
return responses;
};
export default registerPublicDids;
import { AxiosError } from 'axios';
import logger from '../../globalUtils/logger.js';
import { logger } from '../../globalUtils/logger.js';
export function logAxiosError(err: AxiosError) {
if (err.response) {
......@@ -15,7 +15,3 @@ export function logAxiosError(err: AxiosError) {
logger.error('Request error: ', err);
}
}
export default {
logAxiosError,
};
......@@ -31,7 +31,3 @@ export const ledgerNamespaces = {
BCOVRIN_TEST: 'bcovrin:test',
GREEN_LIGHT: 'bcovrin:greenlight',
};
export default {
LEDGER_GENESIS,
};
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment