Skip to content

Task 09: Velocity Network Staging Integration

Overview

Replace all mock/POC API implementations with real Velocity Network staging credentials. The organization has been registered on the Velocity Network Testnet as a Credential Agent Operator and we have received staging access from the Velocity CTO (Andres Olave).


STATUS: UNBLOCKED — Integration Confirmed Working (2026-04-15)

Resolution Summary

The CTO (Andres Olave) confirmed the correct staging integration details. The CIH API at https://stagingcih.velocitycareerlabs.io is the correct service — NOT the Agent Operator API v0.8 at stagingagent.velocitycareerlabs.io (that's the older path).

Confirmed Working Configuration

Item Value
Base URL https://stagingcih.velocitycareerlabs.io
Path prefix /operator (NOT /operator-api/v0.8/)
Auth header Authorization: Bearer 01692F50514EDA42AFE52C0F526B1B2BF383B8631AF77B6340A3631EAAD712BC
Swagger UI https://stagingcih.velocitycareerlabs.io/documentation
Swagger JSON https://stagingcih.velocitycareerlabs.io/documentation/json
API Name Credential Agent v2, v2.0.0

Verified Tenant Creation (Live on Network)

Successfully created our tenant on 2026-04-15:

Tenant ID:       69df3e9a1788f7af31e8edb1
DID:             did:web:stagingregistrar.velocitynetwork.foundation:d:curo-rec.test
Name:            Curo REC Test Organization
CAO DID:         did:web:stagingregistrar.velocitynetwork.foundation:d:ilssi.org
Primary Account: 0xC13c62b227c9Fde4Cd0f1B278a276fA411Fc2Add (blockchain)
Host URL:        https://stagingcih.velocitycareerlabs.io
Created At:      2026-04-15T07:30:34.947Z

3 keys registered with purposes: ISSUING_METADATA, DLT_TRANSACTIONS, EXCHANGES

Verified Request Format

Working tenant creation request:

POST https://stagingcih.velocitycareerlabs.io/operator/tenants/create
Authorization: Bearer 01692F50514EDA42AFE52C0F526B1B2BF383B8631AF77B6340A3631EAAD712BC
Content-Type: application/json

{
  "tenant": {
    "did": "did:web:stagingregistrar.velocitynetwork.foundation:d:curo-rec.test",
    "name": "Curo REC Test Organization",
    "logo": "https://stagingmedia.velocitynetwork.foundation/400x400-lwZVXpKa_jLkS2ClnS7Sm.jpeg",
    "caoDid": "did:web:stagingregistrar.velocitynetwork.foundation:d:ilssi.org"
  },
  "keys": [
    {
      "purposes": ["ISSUING_METADATA"],
      "algorithm": "SECP256K1",
      "encoding": "hex",
      "kidFragment": "#vc-signing-key-1",
      "key": "<from keys.json>"
    },
    {
      "purposes": ["DLT_TRANSACTIONS"],
      "algorithm": "SECP256K1",
      "encoding": "hex",
      "kidFragment": "#eth-account-key-1",
      "key": "<from keys.json>"
    },
    {
      "purposes": ["EXCHANGES"],
      "algorithm": "SECP256K1",
      "encoding": "hex",
      "kidFragment": "#exchange-key-1",
      "key": "<from keys.json>"
    }
  ]
}

Required tenant fields (not obvious from old specs): did, name, logo, caoDid

Key Discoveries From Investigation

Discovery Details
CIH ≠ Agent Operator API v0.8 The CIH (Credential Agent v2) is the replacement — simpler, batch-oriented
Path prefix is /operator Not /operator-api/v0.8/ as shown in old agent-api.v1.json spec
CIH Bearer Token works directly Use as-is in Authorization header — no JWT exchange needed
Auth0 client from keys.json Not needed for CIH API auth — may be used elsewhere
Required tenant fields did, name, logo, caoDid all required
Agent Operator API v0.8 Older path at stagingagent.velocitycareerlabs.io — DO NOT USE for new integration

Staging Credentials Received

Organization Identity

  • Organization DID: did:web:stagingregistrar.velocitynetwork.foundation:d:curo-rec.test
  • CAO DID (for tenant creation): did:web:stagingregistrar.velocitynetwork.foundation:d:ilssi.org
  • Service ID: #vlc-credential-agent-operator-v-1-1

API Endpoints

Service URL
Credential Hub (CIH/Agent v2) https://stagingcih.velocitycareerlabs.io
Registrar Base URL https://stagingregistrar.velocitynetwork.foundation
Agent API (from SDK) https://stagingagent.velocitycareerlabs.io

Authentication

  • CIH Bearer Token: 01692F50514EDA42AFE52C0F526B1B2BF383B8631AF77B6340A3631EAAD712BC
  • Auth0 Client ID: eDu0gHWxto9hGbm2CKhCGbMrGCiucz7B
  • Auth0 Client Secret: stored in keys.json
  • Auth Client Type: agent

Cryptographic Keys (5 keys from keys.json)

Key Fragment Purpose Algorithm Usage
#vc-signing-key-1 ISSUING_METADATA SECP256K1 Sign credentials
#eth-account-key-1 DLT_TRANSACTIONS SECP256K1 Blockchain writes
#exchange-key-1 EXCHANGES SECP256K1 Credential exchanges
#vnf-permissioning-* PERMISSIONING SECP256K1 Access control
#vnf-rotation-* ROTATION SECP256K1 Key rotation

Database Schema Analysis

Organization Entity — NO SCHEMA CHANGES NEEDED

Current columns in organization.entity.ts are already sufficient:

Column Type Status Usage with Staging
velocity_registrar_id varchar, nullable EXISTS Store CIH tenant ID
organization_did varchar, nullable EXISTS Store did:web:stagingregistrar.velocitynetwork.foundation:d:curo-rec.test
velocity_tenant_keys jsonb, default {} EXISTS Store encrypted keys from keys.json
tenant_created_at timestamp, nullable EXISTS Set after successful tenant creation
tenant_status varchar(32), default PENDING EXISTS Track PENDING → ACTIVE flow
status varchar(32) enum EXISTS Organization lifecycle (DRAFT → ISSUER_ENABLED)
kyb_status varchar(32) enum EXISTS KYB workflow tracking
settings jsonb EXISTS Can store VNF-specific config

Credential Offer Entity — NEEDS 2 NEW COLUMNS

Current credential_offer.entity.ts is mostly sufficient, but needs:

Column Type Status Purpose
velocity_offer_id varchar, nullable EXISTS VNF offer ID from CIH
vnf_transaction_id varchar, nullable EXISTS VNF blockchain transaction ID
holder_did varchar, nullable EXISTS Holder's DID
payload jsonb, nullable EXISTS Credential data
claim_method varchar, nullable EXISTS WEB, MOBILE, VNF_APP
webhook_metadata jsonb, nullable EXISTS CIH webhook responses
credential_manifest_id varchar, nullable ADD CIH manifest ID for this offer
deep_link_url text, nullable ADD VNF deep link for mobile wallet claim

New Column Migration

// Migration: AddCredentialManifestFields
await queryRunner.query(`
  ALTER TABLE credential_offers 
  ADD COLUMN IF NOT EXISTS credential_manifest_id VARCHAR NULL;

  ALTER TABLE credential_offers 
  ADD COLUMN IF NOT EXISTS deep_link_url TEXT NULL;
`);

Employee Entity — NO CHANGES NEEDED

All fields map well to EmploymentPastV1.1 credential schema.

All Other Entities — NO CHANGES NEEDED

KYB records, email logs, candidates, templates — all sufficient.


Backend Integration Tasks

Task 1: Secure Keys Storage & Environment Setup

Priority: 1 (Do First) Files to modify: - app/backend/.env.example — add new env vars - app/backend/.gitignore — ensure config/ is ignored

New environment variables (UPDATED with confirmed values):

# Velocity Network - Staging (CIH v2)
VNF_ENVIRONMENT=staging

# CIH API - USE THIS FOR ALL CREDENTIAL OPERATIONS
VNF_CIH_API_URL=https://stagingcih.velocitycareerlabs.io
VNF_CIH_API_PATH_PREFIX=/operator
VNF_CIH_BEARER_TOKEN=01692F50514EDA42AFE52C0F526B1B2BF383B8631AF77B6340A3631EAAD712BC

# Organization Identity (already created)
VNF_ORGANIZATION_DID=did:web:stagingregistrar.velocitynetwork.foundation:d:curo-rec.test
VNF_TENANT_ID=69df3e9a1788f7af31e8edb1
VNF_CAO_DID=did:web:stagingregistrar.velocitynetwork.foundation:d:ilssi.org
VNF_SERVICE_ID=#vlc-credential-agent-operator-v-1-1
VNF_ORG_NAME=Curo REC Test Organization
VNF_ORG_LOGO=https://stagingmedia.velocitynetwork.foundation/400x400-lwZVXpKa_jLkS2ClnS7Sm.jpeg

# Keys
VNF_KEYS_FILE_PATH=./config/velocity-keys.json

# Registrar API (for org profile lookups, credential types, schemas)
VNF_REGISTRAR_BASE_URL=https://stagingregistrar.velocitynetwork.foundation
VNF_REGISTRAR_API_PATH=/api/v0.6
VNF_REGISTRAR_JWT=<Bearer JWT from stagingregistrarapp dashboard - RS256, audience registrar.velocitynetwork.foundation>

# DEPRECATED - DO NOT USE (older Agent Operator API v0.8)
# VNF_AGENT_API_URL=https://stagingagent.velocitycareerlabs.io

# NOTE: Auth0 client from keys.json (clientId, clientSecret) is NOT needed for CIH API auth
# VNF_AUTH0_CLIENT_ID=eDu0gHWxto9hGbm2CKhCGbMrGCiucz7B
# VNF_AUTH0_CLIENT_SECRET=<from keys.json>

Actions: - [ ] Copy keys.json to app/backend/config/velocity-keys.json - [ ] Add config/velocity-keys.json and config/*.json to .gitignore - [ ] Create .env from .env.example with real values - [ ] Update .env.example with placeholder values (no secrets)


Task 2: Velocity Configuration Module

Priority: 2 File: app/backend/src/admin/config/velocity-staging.config.ts (NEW)

import { registerAs } from '@nestjs/config';
import * as fs from 'fs';
import * as path from 'path';

export interface VelocityStagingConfig {
  environment: string;
  cihApiUrl: string;
  cihBearerToken: string;
  organizationDid: string;
  caoDid: string;
  serviceId: string;
  auth0ClientId: string;
  auth0ClientSecret: string;
  registrarBaseUrl: string;
  agentApiUrl: string;
  keys: any[];  // Loaded from keys.json
}

export default registerAs('velocityStaging', (): VelocityStagingConfig => {
  // Load keys from file
  let keys = [];
  const keysPath = process.env.VNF_KEYS_FILE_PATH;
  if (keysPath && fs.existsSync(path.resolve(keysPath))) {
    const keysFile = JSON.parse(fs.readFileSync(path.resolve(keysPath), 'utf8'));
    keys = keysFile.keys || [];
  }

  return {
    environment: process.env.VNF_ENVIRONMENT || 'staging',
    cihApiUrl: process.env.VNF_CIH_API_URL || '',
    cihBearerToken: process.env.VNF_CIH_BEARER_TOKEN || '',
    organizationDid: process.env.VNF_ORGANIZATION_DID || '',
    caoDid: process.env.VNF_CAO_DID || '',
    serviceId: process.env.VNF_SERVICE_ID || '',
    auth0ClientId: process.env.VNF_AUTH0_CLIENT_ID || '',
    auth0ClientSecret: process.env.VNF_AUTH0_CLIENT_SECRET || '',
    registrarBaseUrl: process.env.VNF_REGISTRAR_BASE_URL || '',
    agentApiUrl: process.env.VNF_AGENT_API_URL || '',
    keys,
  };
});

Actions: - [ ] Create the config file above - [ ] Register in AdminModule imports - [ ] Update existing velocity-registrar.config.ts to reference the new config where needed


Task 3: Remove POC Mock Mode from Services

Priority: 3 (After CTO confirms API routes) Files to modify:

3a. velocity-registrar.service.ts (lines 170-185)

Remove POC mode detection and mock tenant response:

// REMOVE this block:
const isPocMode = !agentApiUrl || !apiKey || nodeEnv === 'development' ...
if (isPocMode) {
  return this.createMockTenantResponse(createTenantDto);
}

// REPLACE with: Use CIH staging URL and bearer token from config

Changes: - [ ] Remove isPocMode logic in createTenant() method - [ ] Remove createMockTenantResponse() private method - [ ] Use VelocityStagingConfig.cihApiUrl as base URL - [ ] Use VelocityStagingConfig.cihBearerToken for Authorization header - [ ] Add caoDid to the tenant creation payload - [ ] Update endpoint URL once confirmed by CTO

3b. vnf-sdk.service.ts — Replace MockVCLProvider

Remove the entire MockVCLProvider class (lines 22-72) and replace with real CIH HTTP calls: - [ ] Remove MockVCLProvider class - [ ] Create CihApiClient that makes real HTTP calls to CIH - [ ] Update generateCredentialOffer() to call CIH API - [ ] Update issueCredential() to call CIH API - [ ] Update searchForOrganizations() to call Registrar API (/api/v0.6/organizations/search-profiles) - [ ] Update getCredentialManifest() to call Agent API

3c. crypto-services/fetchers/Fetcher.ts

  • Remove all mock response fallbacks
  • Point to real staging URLs instead of Stoplight mocks
  • Use real keys from config for signing operations

Task 4: Keys Import Service

Priority: 3 File: app/backend/src/admin/services/velocity-keys-import.service.ts (NEW)

Service to import and manage the 5 keys from keys.json: - [ ] Read keys from config (loaded via velocity-staging.config.ts) - [ ] Map keys by purpose for quick lookup: getKeyByPurpose('ISSUING_METADATA') - [ ] Store keys encrypted in organization's velocityTenantKeys column - [ ] Provide key for JWT signing operations (vc-signing-key-1) - [ ] Provide key for exchange operations (exchange-key-1)

Key mapping:

const KEY_PURPOSE_MAP = {
  ISSUING_METADATA: '#vc-signing-key-1',       // For signing credentials
  DLT_TRANSACTIONS: '#eth-account-key-1',       // For blockchain
  EXCHANGES: '#exchange-key-1',                  // For credential exchange
  PERMISSIONING: '#vnf-permissioning-*',         // For access control
  ROTATION: '#vnf-rotation-*',                   // For key rotation
};


Task 5: Update Tenant Service to Use Real Keys

Priority: 4 File: velocity-tenant.service.ts

Changes: - [ ] Update createTenant() to use keys from keys.json instead of generating random ones - [ ] Update generateTenantKeys() — make it an import function, not generation (or keep as fallback) - [ ] Add caoDid to the CreateTenantDto and include it in the API call - [ ] Update service IDs to use #vlc-credential-agent-operator-v-1-1 from config

Updated CreateTenantDto payload:

{
  caoDid: config.caoDid,  // "did:web:stagingregistrar.velocitynetwork.foundation:d:ilssi.org"
  did: organization.organizationDid,
  serviceIds: [config.serviceId],  // ["#vlc-credential-agent-operator-v-1-1"]
  keys: importedKeysFromKeysJson   // All 5 keys from keys.json
}


Task 6: Update CreateTenantDto

Priority: 4 File: app/backend/src/admin/dto/velocity-tenant.dto.ts

Changes: - [ ] Add caoDid field to CreateTenantDto - [ ] Add didDocumentKey optional field to TenantKeyDto (keys.json includes this)

export class CreateTenantDto {
  @IsOptional()
  @IsString()
  caoDid?: string;  // ADD THIS

  @IsArray()
  serviceIds: string[];

  @IsString()
  did: string;

  @IsArray()
  keys: TenantKeyDto[];

  // ... existing fields
}

export class TenantKeyDto {
  @IsArray()
  purposes: string[];

  @IsString()
  algorithm: string;

  @IsString()
  encoding: string;

  @IsString()
  kidFragment: string;

  @IsString()
  key: string;

  @IsOptional()
  didDocumentKey?: Record<string, any>;  // ADD THIS

  @IsOptional()
  @IsBoolean()
  custodied?: boolean;  // ADD THIS
}

Task 7: VNF SDK Service — Initialize with Staging Environment

Priority: 5 File: app/backend/src/shared/services/vnf-sdk.service.ts

Changes: - [ ] Set VCLEnvironment.Staging instead of POC mock environment - [ ] Initialize the real @velocitycareerlabs/vnf-nodejs-wallet-sdk instead of MockVCLProvider - [ ] Pass real keys to crypto services - [ ] Set XVnfProtocolVersion2 (already done)

// Replace MockVCLProvider with:
import VCL from '@velocitycareerlabs/vnf-nodejs-wallet-sdk';

// Initialize with staging:
const initDescriptor = new VCLInitializationDescriptor(
  VCLEnvironment.Staging,
  VCLXVnfProtocolVersion.XVnfProtocolVersion2,
  cryptoServicesDescriptor
);
await VCL.initialize(initDescriptor);

Task 8: Credential Issuance Flow (VNF SDK Pattern)

Priority: 6 (After tasks 3-7 complete) Files: - app/backend/src/issuer/services/credential.service.ts - app/backend/src/issuer/services/manual-credential.service.ts

Based on the VNF SDK sample app, the credential issuance flow is:

Step 1: getCredentialManifest(descriptorByService)
  → Returns VCLCredentialManifest with endpoints

Step 2: generateOffers(generateOffersDescriptor)
  → Uses manifest endpoint to create offers
  → Returns VCLOffers with session token

Step 3: finalizeOffers(finalizeOffersDescriptor, sessionToken)
  → Approves/rejects offers
  → Returns issued VCLVerifiableCredentials

EmploymentPastV1.1 Credential Data Structure (from SDK schemas):

{
  "credentialSubject": {
    "company": {
      "name": "Acme Corp",
      "identifier": {
        "type": "did:web",
        "id": "<org-did>"
      }
    },
    "title": "Software Engineer",
    "startMonthYear": { "month": 1, "year": 2020 },
    "endMonthYear": { "month": 12, "year": 2023 },
    "location": {
      "countryCode": "GB",
      "regionCode": "GB-LND"
    }
  }
}

Employee → Credential Mapping: | Employee Field | CredentialSubject Field | |---------------|----------------------| | organization.name | company.name | | organization.organizationDid | company.identifier.id | | jobTitle | title | | startDate | startMonthYear | | endDate | endMonthYear | | workLocation | location | | department | Can add to description |

Identification Credentials Required: The VNF SDK requires Email/Phone verifiable credentials for identification during offer generation. These are pre-existing VCs (like the AdamSmith JWTs in Constants.ts). For POC, we may need to use test identification VCs.

Actions: - [ ] Create credential data mapper: Employee → EmploymentPastV1.1 format - [ ] Implement getCredentialManifest → generateOffers → finalizeOffers flow - [ ] Store velocityOfferId from the VNF response - [ ] Generate deep link URL for mobile wallet claiming - [ ] Update credential offer status through the flow


Task 9: Migration for New Credential Offer Columns

Priority: 2 File: app/backend/src/migrations/<timestamp>-AddCredentialManifestFields.ts (NEW)

import { MigrationInterface, QueryRunner } from 'typeorm';

export class AddCredentialManifestFields implements MigrationInterface {
  public async up(queryRunner: QueryRunner): Promise<void> {
    await queryRunner.query(`
      ALTER TABLE credential_offers 
      ADD COLUMN IF NOT EXISTS credential_manifest_id VARCHAR NULL;
    `);
    await queryRunner.query(`
      ALTER TABLE credential_offers 
      ADD COLUMN IF NOT EXISTS deep_link_url TEXT NULL;
    `);
  }

  public async down(queryRunner: QueryRunner): Promise<void> {
    await queryRunner.query(`ALTER TABLE credential_offers DROP COLUMN IF EXISTS credential_manifest_id;`);
    await queryRunner.query(`ALTER TABLE credential_offers DROP COLUMN IF EXISTS deep_link_url;`);
  }
}

Frontend Tasks

Task 10: Update Frontend for Real Data

Priority: 7 (After backend integration works)

10a. Organization Detail — Show Real DID

Files: OrganizationDetail.tsx, superadmin/dashboard.tsx, superadmin/organizations/[id].tsx - [ ] Display real organization DID (truncated with copy button) - [ ] Show real tenant status from CIH - [ ] Add "Create Tenant" button (triggers API call to CIH) - [ ] Show VNF service ID badge

10b. Credential Pages — Real Data

Files: credentials/index.tsx, credentials/manual.tsx, ManualCredentialForm.tsx - [ ] Update credential creation to trigger real VNF SDK flow - [ ] Display real Velocity offer IDs - [ ] Show deep link URL / QR code for mobile wallet claiming - [ ] Display credential status from VNF


VNF SDK API Reference (from SDK source code)

Registrar API (confirmed working URLs from Urls.ts)

Base: https://stagingregistrar.velocitynetwork.foundation

GET  /api/v0.6/credential-types                              — List credential types
GET  /schemas/<schema-name>.schema.json                       — Get credential schema
GET  /reference/countries                                     — List countries
POST /api/v0.6/organizations/search-profiles                  — Search organizations
GET  /api/v0.6/resolve-kid/<kid>                              — Resolve key ID
GET  /api/v0.6/form-schemas?credentialType=<type>             — Get form schema
GET  /api/v0.6/organizations/<did>/verified-profile           — Get verified profile
Base: https://stagingagent.velocitycareerlabs.io

GET  /api/holder/v0.6/org/<did>/issue/get-credential-manifest — Get credential manifest
GET  /api/holder/v0.6/org/<did>/inspect/get-presentation-request — Get presentation request

CIH API (PENDING — need CTO confirmation)

Base: https://stagingcih.velocitycareerlabs.io

POST /<TBD>/tenants                     — Create tenant
POST /<TBD>/credential-offers           — Create credential offer
POST /<TBD>/credentials/issue           — Issue credential
velocity-network-testnet://issue?request_uri=<encoded-manifest-url>&issuerDid=<did>

Execution Order (UPDATED — P1.1 Backend Foundation complete)

# Task Depends On Size Status
1 Environment Setup (env vars, keys.json, gitignore) None Small ✅ Done
2 Config Module (velocity-staging.config.ts) Task 1 Small ✅ Done
9 DB Migration (credential_manifest_id, deep_link_url) None Small ✅ Done
4 Keys Import Service Task 2 Medium In progress (config exposes keys already)
6 Update DTOs (caoDid, name, logo, custodied) None Small Pending
5 Tenant Service Update (use real keys + CIH endpoint) Tasks 2, 4 Medium Pending — AES-256-GCM encryption ready
3 Replace Mock Mode (registrar + SDK services) with CIH calls Tasks 2, 5 Large Pending — P1.2 group
7 VNF SDK Init (or skip SDK, use direct HTTP) Task 2 Medium Pending
8 Credential Issuance Flow (CIH batch API) Tasks 3, 5 Large Pending — P1.2 group
10 Frontend Updates Tasks 3-8 Medium After backend

Progress: Backend foundation (env, config, migration, encryption transformer, global exception filter) complete. Next group is CIH HTTP client + wiring the mock services to real endpoints.


Critical Notes

  1. NEVER commit keys.json, bearer tokens, or secrets to git
  2. Email the CTO first — we cannot complete tasks 3 & 8 without knowing the CIH API routes
  3. The VNF SDK (@velocitycareerlabs/vnf-nodejs-wallet-sdk) may handle the API routing internally when initialized with VCLEnvironment.Staging — test this first, it may bypass the need to know CIH routes
  4. keys.json has 5 keys — the existing generateTenantKeys() only generates 3 (ISSUING_METADATA, EXCHANGES, DLT_TRANSACTIONS). We now have 5 including PERMISSIONING and ROTATION
  5. Identification VCs are required for credential offer generation — need test Email/Phone JWTs for staging (available in SDK Constants.ts)
  6. Auth0 client from keys.json is separate from the app's Auth0 — it's for CIH API authentication


Detailed Code Changes Required

What is NOT "just a URL swap" — actual code changes needed

The 13 built steps use mock data. Replacing mocks with real staging APIs requires changes in 4 backend service files (~200 lines removed, ~300 lines added):

File 1: app/backend/src/shared/services/vnf-sdk.service.ts

What Current (Mock) Needs to Become
Lines 22-72 MockVCLProvider class with fake generateCredentialOffer(), issueCredential() Remove entirely
generateCredentialOffer() Returns { id: "offer-123", type: "CredentialOffer" } HTTP call: POST /operator-api/v0.8/tenants/{did}/exchanges → then POST .../offers
issueCredential() Returns { type: "VerifiableCredential", proof: {...} } HTTP call: POST .../offers/complete
searchForOrganizations() Returns hardcoded mock org HTTP call: GET /api/v0.6/organizations/search-profiles on Registrar
getCredentialManifest() Returns { id: "manifest-123" } HTTP call: GET .../exchanges/{exchangeId}
SDK initialization MockVCLProvider.getInstance() VCL.initialize(VCLEnvironment.Staging, ...) or direct HTTP client

File 2: app/backend/src/admin/services/velocity-registrar.service.ts

What Current (Mock) Needs to Become
Lines 170-185 isPocMode detection → returns createMockTenantResponse() Remove POC mode check entirely
Lines 216-226 createMockTenantResponse() method Remove entirely
createTenant() URL ${agentApiUrl}/tenants (undefined) ${agentApiUrl}/operator-api/v0.8/tenants
Auth header Authorization: Bearer ${apiKey} (empty) Authorization: Bearer ${authToken} (🔐 pending CTO)
Request payload { did, serviceIds, keys } Add caoDid field

File 3: app/backend/src/admin/services/velocity-tenant.service.ts

What Current (Mock) Needs to Become
generateTenantKeys() Generates 3 random SECP256K1 keys Import 5 real keys from keys.json
createTenant() payload serviceIds: ["{did}#issuer", "{did}#verifier"] serviceIds: ["#vlc-credential-agent-operator-v-1-1"]
Key purposes Only ISSUING_METADATA, EXCHANGES, DLT_TRANSACTIONS Add PERMISSIONING, ROTATION from keys.json

File 4: app/backend/src/shared/services/vnf-sdk/crypto-services/fetchers/Fetcher.ts

What Current (Mock) Needs to Become
Mock responses Hardcoded fake DID JWK, JWT sign/verify responses Remove all mock fallbacks
API URLs https://stoplight.io/mocks/velocitycareerlabs/... https://stagingregistrar.velocitynetwork.foundation/...
POC mode check Returns mock if VNF_ENVIRONMENT=poc-mock Always make real HTTP calls

New files to create

File Purpose Size
admin/config/velocity-staging.config.ts Unified config loading all staging credentials + keys.json ~40 lines
admin/services/velocity-keys-import.service.ts Load, map, encrypt keys from keys.json by purpose ~80 lines
migrations/<timestamp>-AddCredentialManifestFields.ts Add credential_manifest_id + deep_link_url columns ~20 lines

New credential issuance flow to implement

Currently credential.service.ts and manual-credential.service.ts create a CredentialOffer in the database but don't call any external API. They need to call the Agent Operator API exchange flow:

// NEW: Agent Operator API credential issuance flow
// This replaces the mock generateCredentialOffer() call

async issueCredentialViaAgent(employee: Employee, org: Organization): Promise<CredentialOffer> {
  const agentUrl = config.agentApiUrl; // https://stagingagent.velocitycareerlabs.io
  const tenantDid = org.organizationDid;
  const authToken = await this.getAgentAuthToken(); // 🔐 PENDING CTO

  // Step 1: Start exchange
  const exchange = await this.http.post(
    `${agentUrl}/operator-api/v0.8/tenants/${tenantDid}/exchanges`,
    { type: 'ISSUING' },
    { headers: { Authorization: `Bearer ${authToken}` } }
  );
  const exchangeId = exchange.data.exchange.id;

  // Step 2: Add credential offer
  const offer = await this.http.post(
    `${agentUrl}/operator-api/v0.8/tenants/${tenantDid}/exchanges/${exchangeId}/offers`,
    {
      type: ['EmploymentPastV1.1'],
      offerId: uuidv4(),
      credentialSubject: {
        company: org.organizationDid,
        companyName: { localized: { en: org.name } },
        title: { localized: { en: employee.jobTitle } },
        startMonthYear: {
          month: employee.startDate.getMonth() + 1,
          year: employee.startDate.getFullYear()
        },
        endMonthYear: {
          month: employee.endDate.getMonth() + 1,
          year: employee.endDate.getFullYear()
        },
        location: { countryCode: 'GB' } // derive from employee.workLocation
      }
    },
    { headers: { Authorization: `Bearer ${authToken}` } }
  );

  // Step 3: Complete offers (send to holder)
  await this.http.post(
    `${agentUrl}/operator-api/v0.8/tenants/${tenantDid}/exchanges/${exchangeId}/offers/complete`,
    {},
    { headers: { Authorization: `Bearer ${authToken}` } }
  );

  // Step 4: Get deep link and QR code
  const deepLink = await this.http.get(
    `${agentUrl}/operator-api/v0.8/tenants/${tenantDid}/exchanges/${exchangeId}/deep-link`,
    { headers: { Authorization: `Bearer ${authToken}` } }
  );

  // Step 5: Save to database
  return this.credentialOfferRepo.save({
    orgId: org.id,
    employeeId: employee.id,
    credentialType: 'EmploymentPastV1.1',
    velocityOfferId: offer.data.offerId,
    credentialManifestId: exchangeId,
    deepLinkUrl: deepLink.data.url,
    payload: offer.data.credentialSubject,
    status: CredentialOfferStatus.CREATED,
  });
}

Frontend changes (minimal — after backend works)

File Change Effort
OrganizationDetail.tsx Display real DID (truncated + copy button) Tiny
superadmin/dashboard.tsx Show real tenant status badge Tiny
credentials/index.tsx Display real velocity offer IDs Tiny
credentials/manual.tsx Trigger real issuance flow (same API call, backend handles it) None
claim/[token].tsx Add QR code from deepLinkUrl column Small (GAP-01)

What works as-is with NO changes

Component Why No Changes
All frontend pages (13 pages) Backend API contracts unchanged
Database schema Only 2 new columns, existing columns reused
Employee CRUD + CSV upload Doesn't touch VNF APIs
Email system + templates + tracking Just sends emails with links from DB
Auth0 login, roles, guards Independent of VNF
KYB workflow Independent of VNF
Dashboard search, filter, pagination Reads from same DB tables
Claim landing page Reads from same credential_offers table

Summary: Effort Estimate

Category Effort Blocked?
Environment config (env vars, keys, gitignore) 1 hour No
Config module + keys import service 3 hours No
DB migration (2 columns) 30 min No
DTO updates (caoDid, didDocumentKey) 30 min No
Remove mock mode from registrar service 2 hours Yes — auth
Replace MockVCLProvider with Agent API calls 4 hours Yes — auth
Remove mock fetchers 1 hour Yes — auth
Credential issuance flow (exchange→offer→complete→deeplink) 4 hours Yes — auth
Frontend: QR code on claim page (GAP-01) 2 hours No
Frontend: show real DID/status 1 hour No
Total ~2-3 days 6 of 10 tasks blocked on auth

Reference Documentation

  • Registrar API: https://docs.velocitynetwork.foundation/docs/registrar-api
  • Registrar Walkthrough: https://www.velocitynetwork.foundation/main/registration-registrar-API
  • Issuing with APIs: https://www.velocitynetwork.foundation/main/nrsyfwe4not0b-issue-credentials-with-ap-is
  • CIH Staging: https://stagingcih.velocitycareerlabs.io
  • Agent Operator API Spec (local): docs/requirements/tasks/POC/api-specs/agent-api.v1.json
  • Registrar API Spec (local): docs/requirements/tasks/POC/api-specs/registrar.v1.json
  • Postman Collections (local): docs/requirements/tasks/POC/api-specs/Velocity - Tenant Management*.json
  • VNF SDK Source (local): vnf-wallet-sdk-nodejs-main/
  • SDK URLs Reference: vnf-wallet-sdk-nodejs-main/packages/sdk/src/impl/data/repositories/Urls.ts
  • SDK Sample App Constants: vnf-wallet-sdk-nodejs-main/packages/sample-app/src/Constants.ts
  • POC Application Flow: 12-poc-application-flow.md
  • Consolidated API Reference: 11-consolidated-api-reference.md
  • Gap Analysis: 10-gap-analysis-v3-requirements.md