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
Agent API (from SDK deep links)¶
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
Deep Link Format (for mobile wallet)¶
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¶
- NEVER commit keys.json, bearer tokens, or secrets to git
- Email the CTO first — we cannot complete tasks 3 & 8 without knowing the CIH API routes
- The VNF SDK (
@velocitycareerlabs/vnf-nodejs-wallet-sdk) may handle the API routing internally when initialized withVCLEnvironment.Staging— test this first, it may bypass the need to know CIH routes - keys.json has 5 keys — the existing
generateTenantKeys()only generates 3 (ISSUING_METADATA, EXCHANGES, DLT_TRANSACTIONS). We now have 5 including PERMISSIONING and ROTATION - Identification VCs are required for credential offer generation — need test Email/Phone JWTs for staging (available in SDK Constants.ts)
- 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