Skip to content

Commit

Permalink
Address comments
Browse files Browse the repository at this point in the history
  • Loading branch information
DOBEN committed Sep 19, 2024
1 parent f9f5ad8 commit 0b498fe
Show file tree
Hide file tree
Showing 9 changed files with 7,702 additions and 5,135 deletions.
1 change: 1 addition & 0 deletions compliant-reward-distribution/frontend/.yarnrc.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
nodeLinker: node-modules
1 change: 1 addition & 0 deletions compliant-reward-distribution/frontend/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
"name": "compliance-reward-distribution-front-end",
"version": "1.0.0",
"type": "module",
"packageManager": "[email protected]",
"scripts": {
"dev": "vite",
"build": "tsc && vite build",
Expand Down
74 changes: 43 additions & 31 deletions compliant-reward-distribution/frontend/src/apiReqeuests.ts
Original file line number Diff line number Diff line change
@@ -1,60 +1,72 @@
import { AccountAddress, AtomicStatementV2, CredentialStatement, VerifiablePresentation } from '@concordium/web-sdk';

interface AccountData {
// The account address that was indexed.
/**
* 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;
// The timestamp of the block the event was included in.
blockTime: string;
// The transaction hash that the event was recorded in.
transactionHash: string;
// A boolean specifying if the account has already claimed its rewards (got
// a reward payout). Every account can only claim rewards once.
claimed: boolean;
// A boolean specifying if this account address has submitted all tasks
// and the regulatory conditions have been proven via a ZK proof.
// A manual check of the completed tasks is required now before releasing
// the reward.
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 {
// The account address that submitted the tweet.
accountAddress: AccountAddress.Type;
// A tweet id submitted by the above account address (task 1).
tweetId: string | undefined;
// A boolean specifying if the text content of the tweet is eligible for
// the reward. The content of the text was verified by this backend
// before this flag is set (or will be verified manually).
tweetValid: boolean;
// A version that specifies the setting of the tweet verification. This
// enables us to update the tweet verification logic in the future and
// invalidate older versions.
tweetVerificationVersion: number;
// The timestamp when the tweet was submitted.
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 {
// The account address that submitted the zk proof.
accountAddress: AccountAddress.Type;
// A hash of the concatenated revealed `national_id_number` and
// `nationality` to prevent claiming with different accounts for the
// same identity.
uniquenessHash: string;
// A boolean specifying if 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.
zkProofValid: boolean;
// A version that specifies the setting of the ZK proof during the
// verification. This enables us to update the ZK proof-verification
// logic in the future and invalidate older proofs.
zkProofVerificationVersion: number;
// The timestamp when the ZK proof verification was submitted.
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 {
accountData: AccountData | undefined;
stateData: StateData | undefined;
tweetData: TweetData | undefined;
zkProofData: ZkProofData | undefined;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import { Alert, Button, Form } from 'react-bootstrap';

import { ConcordiumGRPCClient } from '@concordium/web-sdk';

import { getRecentBlock, requestSignature } from '../utils';
import { getRecentBlock, requestSignature, validateTweetUrl } from '../utils';
import { WalletProvider } from '../wallet-connection';
import { SCHEMA_TWEET_MESSAGE } from '../constants';
import { submitTweet } from '../apiReqeuests';
Expand All @@ -15,16 +15,6 @@ interface Props {
grpcClient: ConcordiumGRPCClient | undefined;
}

const checkTweetUrlFormat = (url: string) => {
// eslint-disable-next-line no-useless-escape
const regex = /^https:\/\/(x\.com|twitter\.com)\/[^\/]+\/status\/(\d+)$/;
if (!url.match(regex)) {
throw Error(
`Not a valid tweet URL (expected format: https://x.com/MaxMustermann/status/1818198789817077916 or https://twitter.com/JohnDoe/status/1818198789817077916)`,
);
}
};

export function TweetSubmission(props: Props) {
const { signer, grpcClient, provider } = props;

Expand All @@ -45,8 +35,6 @@ export function TweetSubmission(props: Props) {
setSuccessfulSubmission(undefined);

try {
checkTweetUrlFormat(tweet);

if (!signer) {
throw Error(`'signer' is undefined. Connect your wallet. Have an account in your wallet.`);
}
Expand All @@ -72,11 +60,13 @@ export function TweetSubmission(props: Props) {
<Form.Group className="col mb-3">
<Form.Label>Tweet</Form.Label>
<Form.Control
{...register('tweet', { required: true })}
{...register('tweet', { required: true, validate: validateTweetUrl })}
type="text"
placeholder="https://x.com/JohnDoe/status/1818198789817077916"
/>
{formState.errors.tweet && <Alert variant="info">Tweet is required </Alert>}
{formState.errors.tweet && (
<Alert variant="info">Tweet is required. {formState.errors.tweet.message}</Alert>
)}
<Form.Text />
</Form.Group>

Expand All @@ -87,14 +77,7 @@ export function TweetSubmission(props: Props) {
{error && <Alert variant="danger">{error}</Alert>}

<br />
<Button
variant="info"
id="accountAddress"
disabled={true}
hidden={successfulSubmission === undefined}
>
Success
</Button>
{successfulSubmission && <Alert variant="info">Success</Alert>}
</Form>
</div>
</div>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ export function ZkProofSubmission(props: Props) {

const { blockHash: recentBlockHash, blockHeight: recentBlockHeight } = await getRecentBlock(grpcClient);

const digest = [recentBlockHash, Buffer.from(CONTEXT_STRING)];
const digest = [recentBlockHash.buffer, Buffer.from(CONTEXT_STRING)];
// The zk proof request here is non-interactive (we don't request the challenge from the backend).
// Instead the challenge consists of a recent block hash (so that the proof expires)
// and a context string (to ensure the ZK proof cannot be replayed on different Concordium services).
Expand Down Expand Up @@ -105,14 +105,7 @@ export function ZkProofSubmission(props: Props) {
{error && <Alert variant="danger">{error}</Alert>}

<br />
<Button
variant="info"
id="accountAddress"
disabled={true}
hidden={successfulSubmission === undefined}
>
Success
</Button>
{successfulSubmission && <Alert variant="info">Success</Alert>}
</Form>
</div>
</div>
Expand Down
31 changes: 18 additions & 13 deletions compliant-reward-distribution/frontend/src/utils.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
import { AccountAddress, ConcordiumGRPCClient } from '@concordium/web-sdk';
import { AccountAddress, BlockHash, ConcordiumGRPCClient } from '@concordium/web-sdk';

import { WalletProvider } from './wallet-connection';
import { RECENT_BLOCK_DURATION } from './constants';

interface RecentBlock {
blockHeight: bigint;
blockHash: Uint8Array;
blockHash: BlockHash.Type;
}

/**
Expand All @@ -31,16 +31,7 @@ export async function getRecentBlock(grpcClient: ConcordiumGRPCClient | undefine

const recentBlockHeight = bestBlockHeight.value - RECENT_BLOCK_DURATION;

const recentBlockHash = (
await grpcClient.client.getBlocksAtHeight({
blocksAtHeight: {
oneofKind: 'absolute',
absolute: {
height: { value: recentBlockHeight },
},
},
})
)?.response.blocks[0].value;
const recentBlockHash = ((await grpcClient.getBlocksAtHeight(recentBlockHeight)) as BlockHash.Type[])[0];

if (!recentBlockHash) {
throw Error(`Couldn't get 'recentBlockHash' from chain`);
Expand All @@ -62,7 +53,7 @@ export async function getRecentBlock(grpcClient: ConcordiumGRPCClient | undefine
* with the given schema, if the `provider` is undefined, or if a multi-sig account is used as signer.
*/
export async function requestSignature(
recentBlockHash: Uint8Array,
recentBlockHash: BlockHash.Type,
schema: string,
message: string | string[] | object,
signer: string,
Expand Down Expand Up @@ -97,3 +88,17 @@ export function validateAccountAddress(accountAddress: string | undefined) {
}
}
}

/**
* Validates if a string represents a valid tweet URL.
*
* @param url - An url string of a tweet posted on Twitter.
* @returns An error message if validation fails.
*/
export function validateTweetUrl(url: string) {
// eslint-disable-next-line no-useless-escape
const regex = /^https:\/\/(x\.com|twitter\.com)\/[^\/]+\/status\/(\d+)$/;
if (!url.match(regex)) {
return `Not a valid tweet URL (expected format: https://x.com/MaxMustermann/status/1818198789817077916 or https://twitter.com/JohnDoe/status/1818198789817077916)`;
}
}
19 changes: 14 additions & 5 deletions compliant-reward-distribution/frontend/src/wallet-connection.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import QRCodeModal from '@walletconnect/qrcode-modal';
import { detectConcordiumProvider, WalletApi } from '@concordium/browser-wallet-api-helpers';
import {
AccountTransactionSignature,
BlockHash,
CredentialStatements,
HexString,
serializeTypeValue,
Expand Down Expand Up @@ -38,7 +39,7 @@ export abstract class WalletProvider extends EventEmitter {
abstract signMessage(
accountAddress: string,
message: string | string[] | object,
recentBlockHash: Uint8Array,
recentBlockHash: BlockHash.Type,
schema: string,
): Promise<AccountTransactionSignature>;

Expand Down Expand Up @@ -86,12 +87,16 @@ export class BrowserWalletProvider extends WalletProvider {
async signMessage(
accountAddress: string,
message: string | string[] | object,
recentBlockHash: Uint8Array,
recentBlockHash: BlockHash.Type,
schema: string,
): Promise<AccountTransactionSignature> {
const payload = Buffer.from(
serializeTypeValue(
{ block_hash: Buffer.from(recentBlockHash).toString('hex'), context_string: CONTEXT_STRING, message },
{
block_hash: Buffer.from(recentBlockHash.buffer).toString('hex'),
context_string: CONTEXT_STRING,
message,
},
toBuffer(schema, 'base64'),
).buffer,
).toString('hex');
Expand Down Expand Up @@ -192,7 +197,7 @@ export class WalletConnectProvider extends WalletProvider {
async signMessage(
accountAddress: string,
message: string | string[] | object,
recentBlockHash: Uint8Array,
recentBlockHash: BlockHash.Type,
schema: string,
): Promise<AccountTransactionSignature> {
if (!this.topic) {
Expand All @@ -201,7 +206,11 @@ export class WalletConnectProvider extends WalletProvider {

const payload = Buffer.from(
serializeTypeValue(
{ block_hash: Buffer.from(recentBlockHash).toString('hex'), context_string: CONTEXT_STRING, message },
{
block_hash: Buffer.from(recentBlockHash.buffer).toString('hex'),
context_string: CONTEXT_STRING,
message,
},
toBuffer(schema, 'base64'),
).buffer,
).toString('hex');
Expand Down
Loading

0 comments on commit 0b498fe

Please sign in to comment.