From 7746ceba4fb3f09cc9941783294cb45bfbd7e09e Mon Sep 17 00:00:00 2001 From: nasko25 Date: Mon, 22 Apr 2024 19:02:14 +0300 Subject: [PATCH 1/3] feat(app, platforms): add outdid stamp --- app/.env-example.env | 1 + app/__test-fixtures__/contextTestHelpers.tsx | 4 + app/context/ceramicContext.tsx | 9 + app/public/assets/outdidStampIcon.svg | 1 + iam/.env-example.env | 2 + platforms/src/Outdid/App-Bindings.ts | 46 +++++ platforms/src/Outdid/Providers-config.ts | 25 +++ platforms/src/Outdid/Providers/outdid.ts | 54 ++++++ platforms/src/Outdid/__tests__/outdid.test.ts | 162 ++++++++++++++++++ platforms/src/Outdid/index.ts | 3 + .../Outdid/procedures/outdidVerification.ts | 15 ++ platforms/src/platforms.ts | 2 + platforms/src/procedure-router.ts | 14 ++ types/src/index.d.ts | 6 +- 14 files changed, 342 insertions(+), 2 deletions(-) create mode 100644 app/public/assets/outdidStampIcon.svg create mode 100644 platforms/src/Outdid/App-Bindings.ts create mode 100644 platforms/src/Outdid/Providers-config.ts create mode 100644 platforms/src/Outdid/Providers/outdid.ts create mode 100644 platforms/src/Outdid/__tests__/outdid.test.ts create mode 100644 platforms/src/Outdid/index.ts create mode 100644 platforms/src/Outdid/procedures/outdidVerification.ts diff --git a/app/.env-example.env b/app/.env-example.env index 592ff2f8c7..c7f9524fda 100644 --- a/app/.env-example.env +++ b/app/.env-example.env @@ -14,6 +14,7 @@ NEXT_PUBLIC_PASSPORT_COINBASE_CALLBACK=http://localhost:3000/ NEXT_PUBLIC_PASSPORT_IDENA_CALLBACK=http://localhost:3000/ NEXT_PUBLIC_PASSPORT_IDENA_WEB_APP=https://app.idena.io/ NEXT_PUBLIC_PASSPORT_CIVIC_CALLBACK=http://localhost:3000/ +NEXT_PUBLIC_PASSPORT_OUTDID_CALLBACK=http://localhost:3000/ NEXT_PUBLIC_PASSPORT_SIGNER_URL=http://localhost:8000/ diff --git a/app/__test-fixtures__/contextTestHelpers.tsx b/app/__test-fixtures__/contextTestHelpers.tsx index 003c50dbe3..ef6747b3fd 100644 --- a/app/__test-fixtures__/contextTestHelpers.tsx +++ b/app/__test-fixtures__/contextTestHelpers.tsx @@ -50,6 +50,10 @@ export const makeTestCeramicContext = (initialState?: Partial \ No newline at end of file diff --git a/iam/.env-example.env b/iam/.env-example.env index f2583f413b..0bad107c5a 100644 --- a/iam/.env-example.env +++ b/iam/.env-example.env @@ -23,6 +23,8 @@ COINBASE_CLIENT_ID=MY_COINBASE_CLIENT_ID COINBASE_CLIENT_SECRET=MY_COINBASE_CLIENT_SECRET COINBASE_CALLBACK=http://localhost:3000/ TRUSTA_LABS_ACCESS_TOKEN=trusta_labs_access_token +OUTDID_API_KEY= +OUTDID_API_SECRET= CURRENT_ENV=development EXIT_ON_UNHANDLED_ERROR=true diff --git a/platforms/src/Outdid/App-Bindings.ts b/platforms/src/Outdid/App-Bindings.ts new file mode 100644 index 0000000000..99c991e664 --- /dev/null +++ b/platforms/src/Outdid/App-Bindings.ts @@ -0,0 +1,46 @@ +import axios from "axios"; +import { AppContext, PlatformOptions, ProviderPayload } from "../types"; +import { Platform } from "../utils/platform"; + +export class OutdidPlatform extends Platform { + platformId = "Outdid"; + path = "outdid"; + clientId: string = null; + redirectUri: string = null; + + constructor(options: PlatformOptions = {}) { + super(); + this.clientId = options.clientId as string; + this.redirectUri = options.redirectUri as string; + } + + async getProviderPayload(appContext: AppContext): Promise { + const { successRedirect, requestID } = (await axios.post(`${process.env.NEXT_PUBLIC_PASSPORT_PROCEDURE_URL?.replace( + /\/*?$/, + "" + )}/outdid/connect`, { + callback: `${this.redirectUri}?error=false&code=null&state=outdid`, + userDid: appContext.userDid, + })).data as { successRedirect: string, requestID: string }; + const width = 800; + const height = 900; + const left = appContext.screen.width / 2 - width / 2; + const top = appContext.screen.height / 2 - height / 2; + + // Pass data to the page via props + appContext.window.open( + successRedirect, + "_blank", + `toolbar=no, location=no, directories=no, status=no, menubar=no, resizable=no, copyhistory=no, width=${width}, height=${height}, top=${top}, left=${left}` + ); + + const response = await appContext.waitForRedirect(this); + + return { + requestID, + code: "success", + sessionKey: response.state, + userDid: appContext.userDid, + }; + } +} \ No newline at end of file diff --git a/platforms/src/Outdid/Providers-config.ts b/platforms/src/Outdid/Providers-config.ts new file mode 100644 index 0000000000..473166610d --- /dev/null +++ b/platforms/src/Outdid/Providers-config.ts @@ -0,0 +1,25 @@ +import { PlatformSpec, PlatformGroupSpec, Provider } from "../types"; +import { OutdidProvider } from "./Providers/outdid"; + +export const PlatformDetails: PlatformSpec = { + icon: "./assets/outdidStampIcon.svg", + platform: "Outdid", + name: "Outdid", + description: "Connect to Outdid to verify you are a unique person.", + connectMessage: "Connect Account", +}; + +export const ProviderConfig: PlatformGroupSpec[] = [ + { + platformGroup: "Name of the Stamp platform group", + providers: [ + { + title: "Verify your identity with Outdid", + description: "Outdid uses zero-knowledge cryptography to ensure you are a unique human without revealing any personal information.", + name: "Outdid", + }, + ] + }, +]; + +export const providers: Provider[] = [new OutdidProvider()] \ No newline at end of file diff --git a/platforms/src/Outdid/Providers/outdid.ts b/platforms/src/Outdid/Providers/outdid.ts new file mode 100644 index 0000000000..a881cdcd6a --- /dev/null +++ b/platforms/src/Outdid/Providers/outdid.ts @@ -0,0 +1,54 @@ +import { type Provider, type ProviderOptions } from "../../types"; +import type { RequestPayload, VerifiedPayload } from "@gitcoin/passport-types"; +import axios from "axios"; + +type OutdidVerification = { + uniqueID: string, + verificationName: string, + status: string, + parameters: { uniqueness: boolean }, +} + +export class OutdidProvider implements Provider { + // The type will be determined dynamically, from the options passed in to the constructor + type = "Outdid"; + + // Options can be set here and/or via the constructor + _options: ProviderOptions = {}; + + // construct the provider instance with supplied options + constructor(options: ProviderOptions = {}) { + this._options = { ...this._options, ...options }; + } + + async verify(payload: RequestPayload): Promise { + let valid = false; + let id = ""; + const errors: string[] = []; + try { + const requestID = payload.proofs?.requestID; + + const verificationData: OutdidVerification = await axios.get(`https://api.outdid.io/verification-request?requestID=${requestID}`) + .then((response: {data: OutdidVerification}) => response.data); + + id = verificationData.uniqueID; + const did = verificationData.verificationName; + if (did === undefined || did === "" || did !== payload.proofs?.userDid) { + errors.push("User verification with Outdid failed."); + } else if (id === undefined || id === "" || JSON.stringify(verificationData.parameters) !== JSON.stringify({ uniqueness: true })) { + errors.push("User could not be verified"); + } else { + valid = (verificationData.status === "succeeded"); + } + } catch (e) { + errors.push("Error verifying identity with Outdid: " + String(e)); + valid = false; + } + + return { + valid, + errors, + record: { id }, + }; + } +} \ No newline at end of file diff --git a/platforms/src/Outdid/__tests__/outdid.test.ts b/platforms/src/Outdid/__tests__/outdid.test.ts new file mode 100644 index 0000000000..b4477946d4 --- /dev/null +++ b/platforms/src/Outdid/__tests__/outdid.test.ts @@ -0,0 +1,162 @@ +/* eslint-disable */ +// ---- Test subject +import { RequestPayload } from "@gitcoin/passport-types"; +import { OutdidProvider } from "../Providers/outdid"; +import { outdidRequestVerification } from "../procedures/outdidVerification"; + +// ----- Libs +import axios from "axios"; + +jest.mock("axios"); + +const mockedAxios = axios as jest.Mocked; + +const userDid = "mock user DID"; +const userID = "mock unique user ID"; +const requestID = "11112222"; +const redirect = process.env.NEXT_PUBLIC_PASSPORT_OUTDID_CALLBACK; + +beforeEach(() => { + jest.clearAllMocks(); +}); + +describe("Attempt Outdid verification", function () { + async function mockProvider(mockedRequest: object) { + mockedAxios.get.mockImplementation(async (url, config) => { + return mockedRequest; + }); + + const outdid = new OutdidProvider(); + const outdidPayload = await outdid.verify({ + proofs: { + requestID, + userDid, + }, + } as unknown as RequestPayload); + + expect(mockedAxios.get).toBeCalledTimes(1); + expect(mockedAxios.get).toBeCalledWith( + `https://api.outdid.io/verification-request?requestID=${requestID}`, + ); + + return outdidPayload; + } + + it("handles valid verification attempt", async () => { + const outdidPayload = await mockProvider({ + data: { + verificationName: userDid, + uniqueID: userID, + parameters: { uniqueness: true }, + status: "succeeded", + }, + status: 200 + }); + expect(outdidPayload).toEqual({ + valid: true, + errors: [], + record: { + id: userID, + }, + }); + }); + + it("handles expired verification attempt", async () => { + const outdidPayload = await mockProvider({ + data: { + verificationName: userDid, + uniqueID: userID, + parameters: { uniqueness: true }, + status: "expired", + }, + status: 200 + }); + expect(outdidPayload).toEqual({ + valid: false, + errors: [], + record: { + id: userID, + }, + }); + }); + + it("handles unexpected response from Outdid", async () => { + const outdidPayload = await mockProvider({ + status: 500 + }); + + expect(outdidPayload).toMatchObject({ valid: false }); + }); + + it("result should not be valid in case user DID is not returned from Outdid", async () => { + const outdidPayload = await mockProvider({ + data: { + verificationName: undefined, + uniqueID: userID, + parameters: { uniqueness: true }, + status: "succeeded", + }, + status: 200 + }); + expect(outdidPayload).toMatchObject({ valid: false }); + }); + + + it("result should not be valid in case no parameters are returned from Outdid", async () => { + const outdidPayload = await mockProvider({ + data: { + verificationName: userDid, + parameters: undefined, + status: "failed", + }, + status: 200 + }); + expect(outdidPayload).toMatchObject({ valid: false }); + }); + + it("result should not be valid in case no user identifier is returned from Outdid", async () => { + const outdidPayload = await mockProvider({ + data: { + verificationName: userDid, + uniqueID: undefined, + parameters: { uniqueness: true }, + status: "succeeded", + }, + status: 200 + }); + expect(outdidPayload).toMatchObject({ valid: false }); + }); +}); + + +describe("Attempt Outdid request verification", function () { + it("handles valid verification", async () => { + const successRedirect = "http://example.com"; + mockedAxios.post.mockImplementation(async (url, _, config) => { + return { + data: { + successRedirect, + requestID, + }, + status: 200, + }; + }); + const verificationRequestData = await outdidRequestVerification(userDid, redirect); + + expect(mockedAxios.post).toBeCalledTimes(1); + expect(mockedAxios.post).toBeCalledWith( + `https://api.outdid.io/verification-request?apiKey=${process.env.OUTDID_API_KEY}&apiSecret=${process.env.OUTDID_API_SECRET}`, + expect.objectContaining({ + verificationParameters: { uniqueness: true }, + verificationType: "icao", + verificationName: userDid, + redirect, + }), + ); + + expect(verificationRequestData).toEqual({ + successRedirect, + requestID + }); + }); +}); \ No newline at end of file diff --git a/platforms/src/Outdid/index.ts b/platforms/src/Outdid/index.ts new file mode 100644 index 0000000000..f8c543489a --- /dev/null +++ b/platforms/src/Outdid/index.ts @@ -0,0 +1,3 @@ +export { OutdidPlatform } from "./App-Bindings"; +export { ProviderConfig, PlatformDetails, providers } from "./Providers-config"; +export { OutdidProvider } from "./Providers/outdid"; \ No newline at end of file diff --git a/platforms/src/Outdid/procedures/outdidVerification.ts b/platforms/src/Outdid/procedures/outdidVerification.ts new file mode 100644 index 0000000000..e1fed23ac3 --- /dev/null +++ b/platforms/src/Outdid/procedures/outdidVerification.ts @@ -0,0 +1,15 @@ +import axios from "axios"; + +type OutdidVerificationResponse = { successRedirect: string, requestID: string, userDid?: string }; + +export const outdidRequestVerification = async (userDid: string, redirect: string): Promise => { + // request a verification containing a unique user identifier + return await axios.post(`https://api.outdid.io/verification-request?apiKey=${process.env.OUTDID_API_KEY}&apiSecret=${process.env.OUTDID_API_SECRET}`, { + verificationParameters: { uniqueness: true }, + verificationType: "icao", + verificationName: userDid, + redirect, + }).then((response: { data: OutdidVerificationResponse }) => { + return response.data; + }); +} diff --git a/platforms/src/platforms.ts b/platforms/src/platforms.ts index 0a5b20656d..5db5a0b315 100644 --- a/platforms/src/platforms.ts +++ b/platforms/src/platforms.ts @@ -19,6 +19,7 @@ import * as Holonym from "./Holonym"; import * as Idena from "./Idena"; import * as Civic from "./Civic"; import * as TrustaLabs from "./TrustaLabs"; +import * as Outdid from "./Outdid"; import { PlatformSpec, PlatformGroupSpec, Provider } from "./types"; type PlatformConfig = { @@ -50,6 +51,7 @@ const platforms: Record = { Idena, Civic, TrustaLabs, + Outdid, }; if (process.env.NEXT_PUBLIC_FF_NEW_POAP_STAMPS === "on") { diff --git a/platforms/src/procedure-router.ts b/platforms/src/procedure-router.ts index 4d9de71aaa..0cf775e15a 100644 --- a/platforms/src/procedure-router.ts +++ b/platforms/src/procedure-router.ts @@ -5,6 +5,7 @@ import * as twitterOAuth from "./Twitter/procedures/twitterOauth"; import { triggerBrightidSponsorship, verifyBrightidContextId } from "./Brightid/procedures/brightid"; import path from "path"; import * as idenaSignIn from "./Idena/procedures/idenaSignIn"; +import { outdidRequestVerification } from "./Outdid/procedures/outdidVerification"; export const router = Router(); @@ -149,3 +150,16 @@ router.post("/idena/authenticate", (req: Request, res: Response): void => { } }); }); + +router.post("/outdid/connect", (req: Request, res: Response): void => { + const body = req.body as { userDid?: string, callback?: string }; + if (body && body.userDid && body.callback) { + outdidRequestVerification(body.userDid, body.callback).then((response) => { + res.status(200).send(response); + }).catch((_) => { + res.status(400).send(); + }); + } else { + res.status(400).send(); + } +}); \ No newline at end of file diff --git a/types/src/index.d.ts b/types/src/index.d.ts index bc9c1b4372..c686648d19 100644 --- a/types/src/index.d.ts +++ b/types/src/index.d.ts @@ -357,7 +357,8 @@ export type PLATFORM_ID = | "Civic" | "GrantsStack" | "ZkSync" - | "TrustaLabs"; + | "TrustaLabs" + | "Outdid"; export type PROVIDER_ID = | "Signer" @@ -427,7 +428,8 @@ export type PROVIDER_ID = | "TrustedCitizen" | "ETHAdvocate" | "ETHMaxi" - | "ETHEnthusiast"; + | "ETHEnthusiast" + | "Outdid"; export type StampBit = { bit: number; From f5b934a74752250db74e8ed04b7a2ad3c7c73fa5 Mon Sep 17 00:00:00 2001 From: nasko25 Date: Thu, 25 Apr 2024 00:31:51 +0300 Subject: [PATCH 2/3] fix(platforms): update text --- platforms/src/Outdid/App-Bindings.ts | 4 ++++ platforms/src/Outdid/Providers-config.ts | 4 ++-- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/platforms/src/Outdid/App-Bindings.ts b/platforms/src/Outdid/App-Bindings.ts index 99c991e664..0817c1e68d 100644 --- a/platforms/src/Outdid/App-Bindings.ts +++ b/platforms/src/Outdid/App-Bindings.ts @@ -7,6 +7,10 @@ export class OutdidPlatform extends Platform { path = "outdid"; clientId: string = null; redirectUri: string = null; + + banner = { + heading: "Outdid is an app which scans the NFC chip of your passport and generates a Zero-Knowledge Proof that you are a unique human. Most importantly, all of your private data stays on your phone - not even Outdid can see it :)" + }; constructor(options: PlatformOptions = {}) { super(); diff --git a/platforms/src/Outdid/Providers-config.ts b/platforms/src/Outdid/Providers-config.ts index 473166610d..97307a95aa 100644 --- a/platforms/src/Outdid/Providers-config.ts +++ b/platforms/src/Outdid/Providers-config.ts @@ -5,7 +5,7 @@ export const PlatformDetails: PlatformSpec = { icon: "./assets/outdidStampIcon.svg", platform: "Outdid", name: "Outdid", - description: "Connect to Outdid to verify you are a unique person.", + description: "Outdid's free ZK ID verification brings a strong sybil signal with complete privacy and anonymity.", connectMessage: "Connect Account", }; @@ -14,7 +14,7 @@ export const ProviderConfig: PlatformGroupSpec[] = [ platformGroup: "Name of the Stamp platform group", providers: [ { - title: "Verify your identity with Outdid", + title: "ZK-prove your identity with Outdid", description: "Outdid uses zero-knowledge cryptography to ensure you are a unique human without revealing any personal information.", name: "Outdid", }, From 90df6c35fb65d93cc32e5b9cc5669ce15fd0a8c5 Mon Sep 17 00:00:00 2001 From: nasko25 Date: Tue, 14 May 2024 23:51:27 +0300 Subject: [PATCH 3/3] fix(platforms): update Outdid API --- platforms/src/Outdid/App-Bindings.ts | 6 +++--- platforms/src/Outdid/Providers-config.ts | 1 + platforms/src/Outdid/Providers/outdid.ts | 4 ++-- platforms/src/Outdid/__tests__/outdid.test.ts | 12 ++++++------ .../src/Outdid/procedures/outdidVerification.ts | 4 ++-- 5 files changed, 14 insertions(+), 13 deletions(-) diff --git a/platforms/src/Outdid/App-Bindings.ts b/platforms/src/Outdid/App-Bindings.ts index 0817c1e68d..883788360f 100644 --- a/platforms/src/Outdid/App-Bindings.ts +++ b/platforms/src/Outdid/App-Bindings.ts @@ -19,13 +19,13 @@ export class OutdidPlatform extends Platform { } async getProviderPayload(appContext: AppContext): Promise { - const { successRedirect, requestID } = (await axios.post(`${process.env.NEXT_PUBLIC_PASSPORT_PROCEDURE_URL?.replace( + const { successRedirect, verificationID } = (await axios.post(`${process.env.NEXT_PUBLIC_PASSPORT_PROCEDURE_URL?.replace( /\/*?$/, "" )}/outdid/connect`, { callback: `${this.redirectUri}?error=false&code=null&state=outdid`, userDid: appContext.userDid, - })).data as { successRedirect: string, requestID: string }; + })).data as { successRedirect: string, verificationID: string }; const width = 800; const height = 900; const left = appContext.screen.width / 2 - width / 2; @@ -41,7 +41,7 @@ export class OutdidPlatform extends Platform { const response = await appContext.waitForRedirect(this); return { - requestID, + verificationID, code: "success", sessionKey: response.state, userDid: appContext.userDid, diff --git a/platforms/src/Outdid/Providers-config.ts b/platforms/src/Outdid/Providers-config.ts index 97307a95aa..b077fdc02d 100644 --- a/platforms/src/Outdid/Providers-config.ts +++ b/platforms/src/Outdid/Providers-config.ts @@ -7,6 +7,7 @@ export const PlatformDetails: PlatformSpec = { name: "Outdid", description: "Outdid's free ZK ID verification brings a strong sybil signal with complete privacy and anonymity.", connectMessage: "Connect Account", + website: "https://outdid.io/", }; export const ProviderConfig: PlatformGroupSpec[] = [ diff --git a/platforms/src/Outdid/Providers/outdid.ts b/platforms/src/Outdid/Providers/outdid.ts index a881cdcd6a..24860298dc 100644 --- a/platforms/src/Outdid/Providers/outdid.ts +++ b/platforms/src/Outdid/Providers/outdid.ts @@ -26,9 +26,9 @@ export class OutdidProvider implements Provider { let id = ""; const errors: string[] = []; try { - const requestID = payload.proofs?.requestID; + const verificationID = payload.proofs?.verificationID; - const verificationData: OutdidVerification = await axios.get(`https://api.outdid.io/verification-request?requestID=${requestID}`) + const verificationData: OutdidVerification = await axios.get(`https://api.outdid.io/v1/verification-request?verificationID=${verificationID}`) .then((response: {data: OutdidVerification}) => response.data); id = verificationData.uniqueID; diff --git a/platforms/src/Outdid/__tests__/outdid.test.ts b/platforms/src/Outdid/__tests__/outdid.test.ts index b4477946d4..e6f45aa4b9 100644 --- a/platforms/src/Outdid/__tests__/outdid.test.ts +++ b/platforms/src/Outdid/__tests__/outdid.test.ts @@ -13,7 +13,7 @@ const mockedAxios = axios as jest.Mocked; const userDid = "mock user DID"; const userID = "mock unique user ID"; -const requestID = "11112222"; +const verificationID = "11112222"; const redirect = process.env.NEXT_PUBLIC_PASSPORT_OUTDID_CALLBACK; beforeEach(() => { @@ -29,14 +29,14 @@ describe("Attempt Outdid verification", function () { const outdid = new OutdidProvider(); const outdidPayload = await outdid.verify({ proofs: { - requestID, + verificationID, userDid, }, } as unknown as RequestPayload); expect(mockedAxios.get).toBeCalledTimes(1); expect(mockedAxios.get).toBeCalledWith( - `https://api.outdid.io/verification-request?requestID=${requestID}`, + `https://api.outdid.io/v1/verification-request?verificationID=${verificationID}`, ); return outdidPayload; @@ -136,7 +136,7 @@ describe("Attempt Outdid request verification", function () { return { data: { successRedirect, - requestID, + verificationID, }, status: 200, }; @@ -145,7 +145,7 @@ describe("Attempt Outdid request verification", function () { expect(mockedAxios.post).toBeCalledTimes(1); expect(mockedAxios.post).toBeCalledWith( - `https://api.outdid.io/verification-request?apiKey=${process.env.OUTDID_API_KEY}&apiSecret=${process.env.OUTDID_API_SECRET}`, + `https://api.outdid.io/v1/verification-request?apiKey=${process.env.OUTDID_API_KEY}&apiSecret=${process.env.OUTDID_API_SECRET}`, expect.objectContaining({ verificationParameters: { uniqueness: true }, verificationType: "icao", @@ -156,7 +156,7 @@ describe("Attempt Outdid request verification", function () { expect(verificationRequestData).toEqual({ successRedirect, - requestID + verificationID }); }); }); \ No newline at end of file diff --git a/platforms/src/Outdid/procedures/outdidVerification.ts b/platforms/src/Outdid/procedures/outdidVerification.ts index e1fed23ac3..7ef2030c17 100644 --- a/platforms/src/Outdid/procedures/outdidVerification.ts +++ b/platforms/src/Outdid/procedures/outdidVerification.ts @@ -1,10 +1,10 @@ import axios from "axios"; -type OutdidVerificationResponse = { successRedirect: string, requestID: string, userDid?: string }; +type OutdidVerificationResponse = { successRedirect: string, verificationID: string, userDid?: string }; export const outdidRequestVerification = async (userDid: string, redirect: string): Promise => { // request a verification containing a unique user identifier - return await axios.post(`https://api.outdid.io/verification-request?apiKey=${process.env.OUTDID_API_KEY}&apiSecret=${process.env.OUTDID_API_SECRET}`, { + return await axios.post(`https://api.outdid.io/v1/verification-request?apiKey=${process.env.OUTDID_API_KEY}&apiSecret=${process.env.OUTDID_API_SECRET}`, { verificationParameters: { uniqueness: true }, verificationType: "icao", verificationName: userDid,