Skip to content

Commit

Permalink
Experiment with zod
Browse files Browse the repository at this point in the history
  • Loading branch information
DOBEN committed Sep 19, 2024
1 parent 0b498fe commit c83908f
Show file tree
Hide file tree
Showing 3 changed files with 133 additions and 92 deletions.
3 changes: 2 additions & 1 deletion compliant-reward-distribution/frontend/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,8 @@
"react-router-dom": "^6.22.1",
"rollup-plugin-node-polyfills": "^0.2.1",
"sha256": "^0.2.0",
"vite-plugin-node-polyfills": "^0.21.0"
"vite-plugin-node-polyfills": "^0.21.0",
"zod": "^3.23.8"
},
"devDependencies": {
"@types/json-bigint": "^1.0.4",
Expand Down
214 changes: 123 additions & 91 deletions compliant-reward-distribution/frontend/src/apiReqeuests.ts
Original file line number Diff line number Diff line change
@@ -1,75 +1,108 @@
import { AccountAddress, AtomicStatementV2, CredentialStatement, VerifiablePresentation } from '@concordium/web-sdk';
import { z, ZodArray, ZodObject } from 'zod';

/**
* Represents the stored data in the database of an indexed account (excluding tweet/zkProof data).
*
* @property accountAddress - The account address that was indexed.
* @property blockTime - The timestamp of the block in which the event was included.
* @property transactionHash - The transaction hash where the event was recorded.
* @property claimed - Indicates whether the account has already claimed its reward (each account can only claim rewards once).
* @property pendingApproval - Indicates whether the account has submitted all tasks and proven regulatory
* conditions via a ZK proof. A manual check of the completed tasks is required before releasing the reward.
*/
interface StateData {
accountAddress: AccountAddress.Type;
blockTime: string;
transactionHash: string;
claimed: boolean;
pendingApproval: boolean;
}
import { AtomicStatementV2, CredentialStatement, VerifiablePresentation } from '@concordium/web-sdk';

/**
* Represents the data for a submitted tweet by an account.
*
* @property accountAddress - The account address that submitted the tweet.
* @property tweetId - A tweet ID submitted by the associated account address (task 1).
* @property tweetValid - Indicates whether the tweet's text content is eligible for the reward.
* The content of the text was verified by the backend before this flag is set (or will be verified
* by the admins manually).
* @property tweetVerificationVersion - A version number that specifies the setting for tweet verification,
* enabling updates to the verification logic and enabling invalidating older versions.
* @property tweetSubmitTime - The timestamp when the tweet was submitted.
*/
interface TweetData {
accountAddress: AccountAddress.Type;
tweetId: string | undefined;
tweetValid: boolean;
tweetVerificationVersion: number;
tweetSubmitTime: string;
}
const StateData = z.object({
accountAddress: z.string(),
blockTime: z.string(),
transactionHash: z.string(),
claimed: z.boolean(),
pendingApproval: z.boolean(),
});

/**
* Represents the data for a ZK (Zero-Knowledge) proof submitted by an account.
*
* @property accountAddress - The account address that submitted the ZK proof.
* @property uniquenessHash - A hash of the concatenated revealed `national_id_number` and `nationality`,
* ensuring the same identity cannot claim with different accounts.
* @property zkProofValid - Indicates whether the identity associated with the account is
* eligible for the reward (task 2). An associated ZK proof was verified by this backend before this flag is set.
* @property zkProofVerificationVersion - A version number that specifies the setting used during the ZK proof verification,
* enabling updates to the verification logic and enabling invalidating older versions.
* @property zkProofVerificationSubmitTime - The timestamp when the ZK proof was submitted.
*/
interface ZkProofData {
accountAddress: AccountAddress.Type;
uniquenessHash: string;
zkProofValid: boolean;
zkProofVerificationVersion: number;
zkProofVerificationSubmitTime: string;
}
const TweetData = z.object({
accountAddress: z.string(),
tweetId: z.string().nullable(),
tweetValid: z.boolean(),
tweetVerificationVersion: z.number(),
tweetSubmitTime: z.string(),
});

/**
* Represents the stored data in the database of an account.
*
* @property stateData - Remaining stored data in the database if the account was indexed (excluding tweet/zkProof data).
* @property tweetData - Stored data of a submitted tweet if present.
* @property zkProofData - Stored data of a submitted ZK proof if present.
*/
interface AccountData {
stateData: StateData | undefined;
tweetData: TweetData | undefined;
zkProofData: ZkProofData | undefined;
}
const ZkProofData = z.object({
accountAddress: z.string(),
uniquenessHash: z.string(),
zkProofValid: z.boolean(),
zkProofVerificationVersion: z.number(),
zkProofVerificationSubmitTime: z.string(),
});

const AccountData = z.object({
accountData: StateData.nullable(),
tweetData: TweetData.nullable(),
zkProofData: ZkProofData.nullable(),
});

const AccountDatavec = z.object({ data: z.array(AccountData) });
// /**
// * Represents the stored data in the database of an indexed account (excluding tweet/zkProof data).
// *
// * @property accountAddress - The account address that was indexed.
// * @property blockTime - The timestamp of the block in which the event was included.
// * @property transactionHash - The transaction hash where the event was recorded.
// * @property claimed - Indicates whether the account has already claimed its reward (each account can only claim rewards once).
// * @property pendingApproval - Indicates whether the account has submitted all tasks and proven regulatory
// * conditions via a ZK proof. A manual check of the completed tasks is required before releasing the reward.
// */
// interface StateData {
// accountAddress: AccountAddress.Type;
// blockTime: string;
// transactionHash: string;
// claimed: boolean;
// pendingApproval: boolean;
// }

// /**
// * Represents the data for a submitted tweet by an account.
// *
// * @property accountAddress - The account address that submitted the tweet.
// * @property tweetId - A tweet ID submitted by the associated account address (task 1).
// * @property tweetValid - Indicates whether the tweet's text content is eligible for the reward.
// * The content of the text was verified by the backend before this flag is set (or will be verified
// * by the admins manually).
// * @property tweetVerificationVersion - A version number that specifies the setting for tweet verification,
// * enabling updates to the verification logic and enabling invalidating older versions.
// * @property tweetSubmitTime - The timestamp when the tweet was submitted.
// */
// interface TweetData {
// accountAddress: AccountAddress.Type;
// tweetId: string | undefined;
// tweetValid: boolean;
// tweetVerificationVersion: number;
// tweetSubmitTime: string;
// }

// /**
// * Represents the data for a ZK (Zero-Knowledge) proof submitted by an account.
// *
// * @property accountAddress - The account address that submitted the ZK proof.
// * @property uniquenessHash - A hash of the concatenated revealed `national_id_number` and `nationality`,
// * ensuring the same identity cannot claim with different accounts.
// * @property zkProofValid - Indicates whether the identity associated with the account is
// * eligible for the reward (task 2). An associated ZK proof was verified by this backend before this flag is set.
// * @property zkProofVerificationVersion - A version number that specifies the setting used during the ZK proof verification,
// * enabling updates to the verification logic and enabling invalidating older versions.
// * @property zkProofVerificationSubmitTime - The timestamp when the ZK proof was submitted.
// */
// interface ZkProofData {
// accountAddress: AccountAddress.Type;
// uniquenessHash: string;
// zkProofValid: boolean;
// zkProofVerificationVersion: number;
// zkProofVerificationSubmitTime: string;
// }

// /**
// * Represents the stored data in the database of an account.
// *
// * @property stateData - Remaining stored data in the database if the account was indexed (excluding tweet/zkProof data).
// * @property tweetData - Stored data of a submitted tweet if present.
// * @property zkProofData - Stored data of a submitted ZK proof if present.
// */
// interface AccountData {
// stateData: typeof StateData | undefined;
// tweetData: typeof TweetData | undefined;
// zkProofData: typeof ZkProofData | undefined;
// }

/**
* Generates `POST` and `GET` request options.
Expand Down Expand Up @@ -112,12 +145,7 @@ function createRequestOptions(method: string, body?: string): RequestInit {
* @throws An error if the method is invalid, if the body is incorrectly provided for the method,
* if the parsing of the return value fails, or if the backend responses with an error.
*/
async function sendBackendRequest<T = undefined>(
endpoint: string,
method: string,
parseReturnValue: boolean,
body?: string,
): Promise<T> {
async function sendBackendRequest(endpoint: string, method: string, body?: string): Promise<Response> {
const api = `api/${endpoint}`;

const requestOption = createRequestOptions(method, body);
Expand All @@ -138,16 +166,17 @@ async function sendBackendRequest<T = undefined>(
throw new Error(`Unable to send request to the backend '${api}'; Error: ${JSON.stringify(parsedError)}`);
}

if (parseReturnValue) {
// Parse the response as type `T`
try {
return (await response.json()) as T;
} catch (e) {
throw new Error(`Failed to parse the response from the backend into expected type.`);
}
}
return response;
}

return undefined as unknown as T;
// eslint-disable-next-line @typescript-eslint/no-explicit-any
async function parse<T extends ZodObject<any> | ZodArray<any>>(schema: T, response: Response): Promise<T> {
// Parse the response as type `T` and validate it using the provided Zod schema
try {
return schema.parse(await response.json()) as T;
} catch (e) {
throw new Error(`Failed to parse or validate the response from the backend: ${e}`);
}
}

/**
Expand All @@ -172,7 +201,7 @@ export async function setClaimed(signer: string, signature: string, recentBlockH
},
});

return await sendBackendRequest('setClaimed', 'POST', false, body);
return await sendBackendRequest('setClaimed', 'POST', body);
}

/**
Expand All @@ -192,7 +221,7 @@ export async function getPendingApprovals(
recentBlockHeight: bigint,
limit: number,
offset: number,
): Promise<AccountData[] | undefined> {
): Promise<typeof AccountDatavec> {
const body = JSON.stringify({
signingData: {
signer,
Expand All @@ -205,7 +234,8 @@ export async function getPendingApprovals(
},
});

return await sendBackendRequest<AccountData[]>('getPendingApprovals', 'POST', true, body);
const response = await sendBackendRequest('getPendingApprovals', 'POST', body);
return parse(AccountDatavec, response);
}

/**
Expand All @@ -222,7 +252,7 @@ export async function getAccountData(
accountAddress: string,
signature: string,
recentBlockHeight: bigint,
): Promise<AccountData> {
): Promise<typeof AccountData> {
const body = JSON.stringify({
signingData: {
signer,
Expand All @@ -234,7 +264,8 @@ export async function getAccountData(
},
});

return await sendBackendRequest<AccountData>('getAccountData', 'POST', true, body);
const response = await sendBackendRequest('getAccountData', 'POST', body);
return parse(AccountData, response);
}

/**
Expand All @@ -243,7 +274,8 @@ export async function getAccountData(
* @throws An error if the backend responses with an error, or parsing of the return value fails.
*/
export async function getStatement(): Promise<CredentialStatement> {
const statement = await sendBackendRequest<{ data: AtomicStatementV2[] }>('getZKProofStatements', 'GET', true);
const response = await sendBackendRequest('getZKProofStatements', 'GET');
const statement = (await response.json()) as { data: AtomicStatementV2[] };

const credentialStatement: CredentialStatement = {
idQualifier: {
Expand Down Expand Up @@ -281,7 +313,7 @@ export async function submitTweet(signer: string, signature: string, recentBlock
},
});

return await sendBackendRequest('postTweet', 'POST', false, body);
return await sendBackendRequest('postTweet', 'POST', body);
}

/**
Expand All @@ -298,5 +330,5 @@ export async function submitZkProof(presentation: VerifiablePresentation, recent
presentation: presentation,
});

return await sendBackendRequest('postZKProof', 'POST', false, body);
return await sendBackendRequest('postZKProof', 'POST', body);
}
8 changes: 8 additions & 0 deletions compliant-reward-distribution/frontend/yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -2589,6 +2589,7 @@ __metadata:
vite-plugin-top-level-await: "npm:^1.3.1"
vite-plugin-wasm: "npm:^3.2.2"
vite-tsconfig-paths: "npm:^4.2.1"
zod: "npm:^3.23.8"
languageName: unknown
linkType: soft

Expand Down Expand Up @@ -7595,3 +7596,10 @@ __metadata:
checksum: 10c0/dceb44c28578b31641e13695d200d34ec4ab3966a5729814d5445b194933c096b7ced71494ce53a0e8820685d1d010df8b2422e5bf2cdea7e469d97ffbea306f
languageName: node
linkType: hard

"zod@npm:^3.23.8":
version: 3.23.8
resolution: "zod@npm:3.23.8"
checksum: 10c0/8f14c87d6b1b53c944c25ce7a28616896319d95bc46a9660fe441adc0ed0a81253b02b5abdaeffedbeb23bdd25a0bf1c29d2c12dd919aef6447652dd295e3e69
languageName: node
linkType: hard

0 comments on commit c83908f

Please sign in to comment.