diff --git a/apps/govern/common-util/functions/balance.ts b/apps/govern/common-util/functions/balance.ts
index d31b3dc4..dd2a3bbd 100644
--- a/apps/govern/common-util/functions/balance.ts
+++ b/apps/govern/common-util/functions/balance.ts
@@ -1,20 +1,34 @@
+import isNil from 'lodash/isNil';
+
+/**
+ * Return formatted number with appropriate suffix
+ */
+export const formatWeiNumber = ({
+  value,
+  minimumFractionDigits = 0,
+  maximumFractionDigits = 2,
+}: {
+  value: number | string | undefined;
+  minimumFractionDigits?: number;
+  maximumFractionDigits?: number;
+}) => {
+  if (isNil(value) || Number(value) === 0) return '0';
+
+  return new Intl.NumberFormat('en', {
+    notation: 'compact',
+    minimumFractionDigits,
+    maximumFractionDigits,
+  }).format(Number(value));
+};
+
 /**
- * @param {number} balanceInWei
- * @returns formatted balance with appropriate suffix
+ * Converts a number to a comma separated format
+ * eg: 1000000 => 1,000,000, 12345.67 => 12,345.67
  */
-export const formatWeiBalance = (balanceInWei: number | string) => {
-  const formatNumberWithSuffix = (number: number) => {
-    if (number >= 1e9) {
-      return `${Math.floor((number / 1e9) * 10) / 10}B`;
-    }
-    if (number >= 1e6) {
-      return `${Math.floor((number / 1e6) * 10) / 10}M`;
-    }
-    if (number >= 1e3) {
-      return `${Math.floor((number / 1e3) * 10) / 10}k`;
-    }
-    return Math.floor(number * 10) / 10;
-  };
+export const getCommaSeparatedNumber = (value: number | string | undefined) => {
+  if (isNil(value) || Number(value) === 0) return '0';
 
-  return formatNumberWithSuffix(parseFloat(`${balanceInWei}`));
+  return new Intl.NumberFormat('en', {
+    maximumFractionDigits: 2,
+  }).format(Number(value));
 };
diff --git a/apps/govern/common-util/functions/index.ts b/apps/govern/common-util/functions/index.ts
index ed7f47e7..ca91ccfb 100644
--- a/apps/govern/common-util/functions/index.ts
+++ b/apps/govern/common-util/functions/index.ts
@@ -3,3 +3,4 @@ export * from './frontend-library';
 export * from './requests';
 export * from './web3';
 export * from './balance';
+export * from './time';
diff --git a/apps/govern/common-util/functions/requests.ts b/apps/govern/common-util/functions/requests.ts
index 228b6cc5..d56ae7e5 100644
--- a/apps/govern/common-util/functions/requests.ts
+++ b/apps/govern/common-util/functions/requests.ts
@@ -1,5 +1,6 @@
 import { readContract, readContracts } from '@wagmi/core';
-import { AbiFunction } from 'viem';
+import { ethers } from 'ethers';
+import { AbiFunction, TransactionReceipt } from 'viem';
 import { Address } from 'viem';
 import { mainnet } from 'viem/chains';
 
@@ -13,7 +14,7 @@ import { RPC_URLS } from 'common-util/constants/rpcs';
 
 import { getAddressFromBytes32 } from './addresses';
 import { getUnixNextWeekStartTimestamp } from './time';
-import { getVoteWeightingContract } from './web3';
+import { getOlasContract, getVeOlasContract, getVoteWeightingContract } from './web3';
 
 type VoteForNomineeWeightsParams = {
   account: Address | undefined;
@@ -113,3 +114,177 @@ export const checkLockExpired = async (account: Address) => {
 
   return result ? nextWeek >= (result as number) : false;
 };
+
+/**
+ * Approve amount of OLAS to be used
+ */
+export const approveOlasByOwner = ({ account, amount }: { account: Address; amount: bigint }) =>
+  new Promise((resolve, reject) => {
+    const contract = getOlasContract();
+    const spender = (VE_OLAS.addresses as Record<number, string>)[mainnet.id];
+    const fn = contract.methods.approve(spender, amount).send({ from: account });
+
+    sendTransaction(fn, account, {
+      supportedChains: SUPPORTED_CHAINS,
+      rpcUrls: RPC_URLS,
+    })
+      .then((response) => {
+        resolve(response);
+      })
+      .catch((e) => {
+        window.console.log('Error occurred on approving OLAS by owner');
+        reject(e);
+      });
+  });
+
+/**
+ * Check if `Approve` button can be clicked; `allowance` should be greater than or equal to the amount
+ */
+export const hasSufficientTokensRequest = ({
+  account,
+  amount,
+}: {
+  account: Address;
+  amount: number;
+}) =>
+  new Promise((resolve, reject) => {
+    const contract = getOlasContract();
+    const spender = (VE_OLAS.addresses as Record<number, string>)[mainnet.id];
+
+    contract.methods
+      .allowance(account, spender)
+      .call()
+      .then((response: bigint) => {
+        const responseInBg = ethers.toBigInt(response);
+
+        // Resolve false if the response amount is zero
+        if (responseInBg === ethers.toBigInt(0)) {
+          resolve(false);
+          return;
+        }
+
+        const amountBN = ethers.parseUnits(`${amount}`);
+
+        // check if the allowance is greater than or equal to the amount input
+        resolve(responseInBg >= amountBN);
+      })
+      .catch((e: Error) => {
+        window.console.log('Error occurred on calling `allowance` method');
+        reject(e);
+      });
+  });
+
+/**
+ * Create lock for veOLAS
+ */
+export const createLockRequest = async ({
+  account,
+  amount,
+  unlockTime,
+}: {
+  account: Address;
+  amount: string;
+  unlockTime: number;
+}) => {
+  const contract = getVeOlasContract();
+
+  try {
+    const createLockFn = contract.methods.createLock(amount, unlockTime);
+    const estimatedGas = await getEstimatedGasLimit(createLockFn, account);
+    const fn = createLockFn.send({ from: account, gasLimit: estimatedGas });
+
+    const response = await sendTransaction(fn, account, {
+      supportedChains: SUPPORTED_CHAINS,
+      rpcUrls: RPC_URLS,
+    });
+
+    return (response as TransactionReceipt)?.transactionHash;
+  } catch (error) {
+    window.console.log('Error occurred on creating lock for veOLAS');
+    throw error;
+  }
+};
+
+/**
+ * Increase Olas amount locked without modifying the lock time
+ */
+export const updateIncreaseAmount = async ({
+  account,
+  amount,
+}: {
+  account: Address;
+  amount: string;
+}) => {
+  const contract = getVeOlasContract();
+
+  try {
+    const increaseAmountFn = contract.methods.increaseAmount(amount);
+    const estimatedGas = await getEstimatedGasLimit(increaseAmountFn, account);
+    const fn = increaseAmountFn.send({ from: account, gasLimit: estimatedGas });
+
+    const response = await sendTransaction(fn, account, {
+      supportedChains: SUPPORTED_CHAINS,
+      rpcUrls: RPC_URLS,
+    });
+
+    return (response as TransactionReceipt)?.transactionHash;
+  } catch (e) {
+    window.console.log('Error occurred on increasing amount with estimated gas');
+    throw e;
+  }
+};
+
+/**
+ * Increase the unlock time without modifying the amount
+ */
+export const updateIncreaseUnlockTime = async ({
+  account,
+  time,
+}: {
+  account: Address;
+  time: number;
+}) => {
+  const contract = getVeOlasContract();
+
+  try {
+    const increaseUnlockTimeFn = contract.methods.increaseUnlockTime(time);
+    const estimatedGas = await getEstimatedGasLimit(increaseUnlockTimeFn, account);
+    const fn = increaseUnlockTimeFn.send({
+      from: account,
+      gasLimit: estimatedGas,
+    });
+
+    const response = await sendTransaction(fn, account, {
+      supportedChains: SUPPORTED_CHAINS,
+      rpcUrls: RPC_URLS,
+    });
+
+    return (response as TransactionReceipt)?.transactionHash;
+  } catch (error) {
+    window.console.log('Error occurred on increasing unlock time');
+    throw error;
+  }
+};
+
+/**
+ * Withdraw VeOlas
+ */
+export const withdrawVeolasRequest = async ({ account }: { account: Address }) => {
+  const contract = getVeOlasContract();
+
+  try {
+    const withdrawFn = contract.methods.withdraw();
+    const estimatedGas = await getEstimatedGasLimit(withdrawFn, account);
+    const fn = withdrawFn.send({ from: account, gasLimit: estimatedGas });
+
+    const response = await sendTransaction(fn, account, {
+      supportedChains: SUPPORTED_CHAINS,
+      rpcUrls: RPC_URLS,
+    });
+
+    return (response as TransactionReceipt)?.transactionHash;
+  } catch (error) {
+    window.console.log('Error occurred on withdrawing veOlas');
+    throw error;
+  }
+};
diff --git a/apps/govern/common-util/functions/time.ts b/apps/govern/common-util/functions/time.ts
index b2629f4b..019c38d7 100644
--- a/apps/govern/common-util/functions/time.ts
+++ b/apps/govern/common-util/functions/time.ts
@@ -1,3 +1,5 @@
+import { NA } from 'libs/util-constants/src';
+
 // Returns the closest Thursday in the future
 // which is the start of the next week by Unix time
 export const getUnixNextWeekStartTimestamp = () => {
@@ -25,3 +27,78 @@ export const getUnixWeekStartTimestamp = () => {
 
   return result.getTime() / 1000;
 };
+
+const MONTHS = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'];
+
+/**
+ * Get formatted date from milliseconds
+ * example, 1678320000000 => Mar 09 '23
+ */
+export function getFormattedDate(ms: number): string {
+  if (ms == 0) return NA;
+
+  const date = new Date(ms);
+  const month = MONTHS[date.getMonth()];
+  const day = date.getUTCDate();
+  const year = date.getUTCFullYear() % 100; // Get last two digits of year
+
+  return (
+    month +
+    ' ' +
+    (day < 10 ? '0' : '') +
+    day.toString() +
+    " '" +
+    (year < 10 ? '0' : '') +
+    year.toString()
+  );
+}
+
+/**
+ * Get formatted date from milliseconds including time
+ * example, 1678320000000 => Mar 09 '2023 16:00
+ */
+export function getFullFormattedDate(ms: number): string {
+  if (ms == 0) return NA;
+
+  const date = new Date(ms);
+  const month = MONTHS[date.getMonth()];
+  const day = date.getUTCDate();
+  const year = date.getUTCFullYear();
+  const hours = date.getUTCHours();
+  const minutes = date.getUTCMinutes();
+
+  return (
+    month +
+    ' ' +
+    (day < 10 ? '0' : '') +
+    day.toString() +
+    ' ' +
+    year.toString() +
+    ', ' +
+    (hours < 10 ? '0' : '') +
+    hours.toString() +
+    ':' +
+    (minutes < 10 ? '0' : '') +
+    minutes.toString()
+  );
+}
+
+export const dateInMs = (time: number) => {
+  if (!time) return 0;
+  return Math.round(new Date(time).getTime());
+};
+
+/*
+ * Returns the remaining time in seconds between unlockTime and the current time.
+ * Steps:
+ * 1. Convert unlockTime to a timestamp
+ * 2. Get the current time as a timestamp
+ * 3. Calculate the difference between the future timestamp and the current timestamp, convert to seconds.
+ */
+export const getRemainingTimeInSeconds = (unlockTime?: number) => {
+  if (!unlockTime) return 0;
+
+  const futureDateInTimeStamp = new Date(unlockTime).getTime();
+  const todayDateInTimeStamp = new Date().getTime();
+  return Math.round((futureDateInTimeStamp - todayDateInTimeStamp) / 1000);
+};
diff --git a/apps/govern/common-util/functions/web3.ts b/apps/govern/common-util/functions/web3.ts
index 3c8c52fe..3362c14a 100644
--- a/apps/govern/common-util/functions/web3.ts
+++ b/apps/govern/common-util/functions/web3.ts
@@ -1,7 +1,8 @@
+import { mainnet } from 'viem/chains';
 import Web3 from 'web3';
 import { AbiItem } from 'web3-utils';
 
-import { VOTE_WEIGHTING } from 'libs/util-contracts/src/lib/abiAndAddresses';
+import { OLAS, VE_OLAS, VOTE_WEIGHTING } from 'libs/util-contracts/src/lib/abiAndAddresses';
 
 import { getChainId, getProvider } from 'common-util/functions/frontend-library';
 
@@ -26,9 +27,22 @@ const getContract = (abi: AbiItem[], contractAddress: string, chainId?: number)
 };
 
 export const getVoteWeightingContract = () => {
-  const { chainId } = getWeb3Details();
   const abi = VOTE_WEIGHTING.abi as AbiItem[];
-  const address = (VOTE_WEIGHTING.addresses as Record<number, string>)[chainId as number];
+  const address = (VOTE_WEIGHTING.addresses as Record<number, string>)[mainnet.id];
+  const contract = getContract(abi, address);
+  return contract;
+};
+
+export const getOlasContract = () => {
+  const abi = OLAS.abi as AbiItem[];
+  const address = (OLAS.addresses as Record<number, string>)[mainnet.id];
+  const contract = getContract(abi, address);
+  return contract;
+};
+
+export const getVeOlasContract = () => {
+  const abi = VE_OLAS.abi as AbiItem[];
+  const address = (VE_OLAS.addresses as Record<number, string>)[mainnet.id];
   const contract = getContract(abi, address);
   return contract;
 };
diff --git a/apps/govern/components/Contracts/ContractsList.spec.tsx b/apps/govern/components/Contracts/ContractsList.spec.tsx
index 69a0766c..8f4f4537 100644
--- a/apps/govern/components/Contracts/ContractsList.spec.tsx
+++ b/apps/govern/components/Contracts/ContractsList.spec.tsx
@@ -89,11 +89,11 @@ describe('<ContractsList />', () => {
 
     // current weight column
     expect(screen.getByText(/10.12%/)).toBeInTheDocument();
-    expect(screen.getByText(/298.8k veOLAS/)).toBeInTheDocument();
+    expect(screen.getByText(/298.89K veOLAS/)).toBeInTheDocument();
 
     // next weight column
     expect(screen.getByText(/25.56%/)).toBeInTheDocument();
-    expect(screen.getByText(/297.4k veOLAS/)).toBeInTheDocument();
+    expect(screen.getByText(/297.43K veOLAS/)).toBeInTheDocument();
   });
 
   describe('Already voted', () => {
diff --git a/apps/govern/components/Contracts/ContractsList.tsx b/apps/govern/components/Contracts/ContractsList.tsx
index 4c0249bb..78ea8cc2 100644
--- a/apps/govern/components/Contracts/ContractsList.tsx
+++ b/apps/govern/components/Contracts/ContractsList.tsx
@@ -7,7 +7,7 @@ import { useAccount } from 'wagmi';
 
 import { CHAIN_NAMES } from 'libs/util-constants/src';
 
-import { formatWeiBalance } from 'common-util/functions/balance';
+import { formatWeiNumber } from 'common-util/functions/balance';
 import { NextWeekTooltip } from 'components/NextWeekTooltip';
 import { useVotingPower } from 'hooks/useVotingPower';
 import { useAppSelector } from 'store/index';
@@ -59,7 +59,9 @@ const getColumns = ({
       render: (currentWeight) => (
         <Space size={2} direction="vertical">
           <Text>{`${currentWeight?.percentage.toFixed(2)}%`}</Text>
-          <Text type="secondary">{`${formatWeiBalance(currentWeight?.value)} veOLAS`}</Text>
+          <Text type="secondary">{`${formatWeiNumber({
+            value: currentWeight?.value,
+          })} veOLAS`}</Text>
         </Space>
       ),
     },
@@ -70,7 +72,7 @@ const getColumns = ({
       render: (nextWeight) => (
         <Space size={2} direction="vertical">
           <Text>{`${nextWeight?.percentage.toFixed(2)}%`}</Text>
-          <Text type="secondary">{`${formatWeiBalance(nextWeight?.value)} veOLAS`}</Text>
+          <Text type="secondary">{`${formatWeiNumber({ value: nextWeight?.value })} veOLAS`}</Text>
         </Space>
       ),
     },
diff --git a/apps/govern/components/Layout/Balance.tsx b/apps/govern/components/Layout/Balance.tsx
index a94ae862..5a8f24f0 100644
--- a/apps/govern/components/Layout/Balance.tsx
+++ b/apps/govern/components/Layout/Balance.tsx
@@ -5,7 +5,7 @@ import { useAccount } from 'wagmi';
 import { COLOR } from 'libs/ui-theme/src/lib/ui-theme';
 import { MEMBER_URL, UNICODE_SYMBOLS } from 'libs/util-constants/src';
 
-import { formatWeiBalance } from 'common-util/functions';
+import { formatWeiNumber } from 'common-util/functions';
 import { useVotingPower } from 'hooks/index';
 
 const { Text, Paragraph } = Typography;
@@ -35,7 +35,7 @@ export const Balance = () => {
           <InfoCircleOutlined className="mr-8" />
           Your voting power:
         </Text>
-        <Text strong>{formatWeiBalance(data)} veOLAS</Text>
+        <Text strong>{formatWeiNumber({ value: data })} veOLAS</Text>
       </Button>
     </Tooltip>
   );
diff --git a/apps/govern/components/Layout/Menu.tsx b/apps/govern/components/Layout/Menu.tsx
index ccd01329..b070112c 100644
--- a/apps/govern/components/Layout/Menu.tsx
+++ b/apps/govern/components/Layout/Menu.tsx
@@ -11,6 +11,7 @@ interface MenuItem {
 const items: MenuItem[] = [
   { label: 'Staking Contracts', key: 'contracts', path: '/contracts' },
   { label: 'Proposals', key: 'proposals', path: '/proposals' },
+  { label: 'veOLAS', key: 'veolas', path: '/veolas' },
   { label: 'Docs', key: 'docs', path: '/docs' },
 ];
 
diff --git a/apps/govern/components/VeOlas/ApproveOlasModal.tsx b/apps/govern/components/VeOlas/ApproveOlasModal.tsx
new file mode 100644
index 00000000..1c3e0d44
--- /dev/null
+++ b/apps/govern/components/VeOlas/ApproveOlasModal.tsx
@@ -0,0 +1,75 @@
+import { Alert, Button, Flex, Modal } from 'antd';
+import { ethers } from 'ethers';
+import { useState } from 'react';
+import { useAccount } from 'wagmi';
+
+import { notifyError } from 'libs/util-functions/src';
+
+import { approveOlasByOwner } from 'common-util/functions';
+
+type CreateLockModalProps = {
+  isModalVisible: boolean;
+  setIsModalVisible: (value: boolean) => void;
+  amountInEth: bigint;
+  onApprove: () => void;
+};
+
+export const ApproveOlasModal = ({
+  isModalVisible,
+  setIsModalVisible,
+  amountInEth,
+  onApprove,
+}: CreateLockModalProps) => {
+  const { address } = useAccount();
+
+  const [isApproving, setIsApproving] = useState(false);
+
+  const handleApprove = async () => {
+    if (!address) return;
+
+    try {
+      setIsApproving(true);
+      const amountBN = ethers.parseUnits(`${amountInEth}`, 'ether');
+
+      await approveOlasByOwner({
+        account: address,
+        amount: amountBN,
+      });
+
+      onApprove();
+    } catch (error) {
+      console.error(error);
+      notifyError();
+    } finally {
+      setIsApproving(false);
+      setIsModalVisible(false);
+    }
+  };
+
+  return (
+    <Modal
+      title="Approve OLAS"
+      open={isModalVisible}
+      footer={null}
+      onCancel={() => setIsModalVisible(false)}
+    >
+      <Alert
+        className="mb-16"
+        message="Before increasing the amount, an approval for OLAS is required. Please approve to proceed."
+        type="warning"
+      />
+
+      <Flex justify="end">
+        <Button
+          type="primary"
+          htmlType="submit"
+          loading={isApproving}
+          onClick={handleApprove}
+          size="large"
+        >
+          Approve
+        </Button>
+      </Flex>
+    </Modal>
+  );
+};
diff --git a/apps/govern/components/VeOlas/CreateLockModal.tsx b/apps/govern/components/VeOlas/CreateLockModal.tsx
new file mode 100644
index 00000000..9118f33c
--- /dev/null
+++ b/apps/govern/components/VeOlas/CreateLockModal.tsx
@@ -0,0 +1,166 @@
+import { Alert, Button, Divider, Flex, Form, Modal } from 'antd';
+import { ethers } from 'ethers';
+import { useState } from 'react';
+
+import { notifyError, notifySuccess } from 'libs/util-functions/src';
+
+import {
+  createLockRequest,
+  dateInMs,
+  getRemainingTimeInSeconds,
+  hasSufficientTokensRequest,
+} from 'common-util/functions';
+import { useFetchBalances } from 'hooks/useFetchBalances';
+
+import { ApproveOlasModal } from './ApproveOlasModal';
+import { MaxButton } from './MaxButton';
+import { OlasAmountInput } from './OlasAmountInput';
+import { ProjectedVeOlas } from './ProjectedVeOlas';
+import { UnlockTimeInput } from './UnlockTimeInput';
+
+type CreateLockModalProps = {
+  isModalVisible: boolean;
+  setIsModalVisible: (value: boolean) => void;
+};
+
+type FormValues = {
+  amount: number;
+  unlockTime: number;
+};
+
+export const CreateLockModal = ({ isModalVisible, setIsModalVisible }: CreateLockModalProps) => {
+  const [form] = Form.useForm();
+  const { account, lockedEnd, olasBalance, veOlasBalance, refetch } = useFetchBalances();
+
+  const [isLoading, setIsLoading] = useState(false);
+  const [isApproveModalVisible, setIsApproveModalVisible] = useState(false);
+
+  const amountInEth = Form.useWatch('amount', form);
+  const unlockTime = dateInMs(Form.useWatch('unlockTime', form));
+
+  const handleClose = () => {
+    setIsModalVisible(false);
+  };
+
+  const onCreateLock = async () => {
+    if (!account) return;
+
+    setIsLoading(true);
+
+    const txHash = await createLockRequest({
+      amount: ethers.parseUnits(`${amountInEth}`, 18).toString(),
+      unlockTime: getRemainingTimeInSeconds(unlockTime),
+      account,
+    });
+
+    notifySuccess('Lock created successfully!', `Transaction Hash: ${txHash}`);
+
+    // once the lock is created, refetch the data
+    refetch();
+
+    handleClose();
+    setIsLoading(false);
+  };
+
+  const onFinish = async ({ amount }: FormValues) => {
+    if (!account) return;
+
+    try {
+      setIsLoading(true);
+
+      const hasSufficientTokens = await hasSufficientTokensRequest({
+        account,
+        amount,
+      });
+
+      if (!hasSufficientTokens) {
+        setIsLoading(false);
+        setIsApproveModalVisible(true);
+        return;
+      }
+
+      await onCreateLock();
+    } catch (error) {
+      window.console.error(error);
+      notifyError();
+    } finally {
+      setIsLoading(false);
+    }
+  };
+
+  const cannotCreateLock = veOlasBalance !== undefined && Number(veOlasBalance) !== 0;
+
+  return (
+    <Modal
+      title="Lock OLAS for veOLAS"
+      open={isModalVisible}
+      footer={null}
+      onCancel={handleClose}
+      destroyOnClose
+    >
+      <Form
+        form={form}
+        layout="vertical"
+        autoComplete="off"
+        name="create-lock-form"
+        requiredMark={false}
+        onFinish={onFinish}
+      >
+        <div className="mb-24">
+          <OlasAmountInput olasBalance={olasBalance} />
+          <MaxButton
+            olasBalance={olasBalance}
+            onMaxClick={() => {
+              form.setFieldsValue({ amount: olasBalance });
+              form.validateFields(['amount']);
+            }}
+          />
+        </div>
+
+        <UnlockTimeInput startDate={lockedEnd} />
+
+        <Divider className="mb-8" />
+
+        <ProjectedVeOlas olasAmount={amountInEth} unlockTime={unlockTime} />
+
+        <Divider className="mt-8" />
+
+        <Flex gap={12} justify="end" className="mb-4">
+          <Button type="default" onClick={handleClose} size="large">
+            Cancel
+          </Button>
+          <Form.Item className="mb-0">
+            <Button
+              type="primary"
+              htmlType="submit"
+              size="large"
+              disabled={cannotCreateLock || !account}
+              loading={isLoading}
+            >
+              Lock
+            </Button>
+          </Form.Item>
+        </Flex>
+      </Form>
+
+      {!account && (
+        <Alert message="To add, first connect wallet" type="warning" className="mt-16" />
+      )}
+
+      {cannotCreateLock && (
+        <Alert
+          message="Amount already locked, please wait until the lock expires."
+          type="warning"
+          className="mt-16"
+        />
+      )}
+
+      <ApproveOlasModal
+        isModalVisible={isApproveModalVisible}
+        setIsModalVisible={setIsApproveModalVisible}
+        amountInEth={amountInEth}
+        onApprove={onCreateLock}
+      />
+    </Modal>
+  );
+};
diff --git a/apps/govern/components/VeOlas/IncreaseLockModal/IncreaseAmount.tsx b/apps/govern/components/VeOlas/IncreaseLockModal/IncreaseAmount.tsx
new file mode 100644
index 00000000..197ce5ed
--- /dev/null
+++ b/apps/govern/components/VeOlas/IncreaseLockModal/IncreaseAmount.tsx
@@ -0,0 +1,144 @@
+import { Button, Divider, Flex, Form } from 'antd';
+import { ethers } from 'ethers';
+import { useState } from 'react';
+
+import { notifyError, notifySuccess } from 'libs/util-functions/src';
+
+import { hasSufficientTokensRequest, updateIncreaseAmount } from 'common-util/functions';
+import { useFetchBalances } from 'hooks/useFetchBalances';
+
+import { ApproveOlasModal } from '../ApproveOlasModal';
+import { MaxButton } from '../MaxButton';
+import { OlasAmountInput } from '../OlasAmountInput';
+import { ProjectedVeOlas } from '../ProjectedVeOlas';
+import { useVeolasComponents } from '../useVeolasComponents';
+
+type IncreaseAmountProps = {
+  closeModal: () => void;
+};
+
+type FormValues = {
+  amount: number;
+};
+
+export const IncreaseAmount = ({ closeModal }: IncreaseAmountProps) => {
+  const [form] = Form.useForm();
+  const { account, lockedEnd, olasBalance, veOlasBalance, refetch } = useFetchBalances();
+  const { getLockedAmountComponent } = useVeolasComponents();
+
+  const [isLoading, setIsLoading] = useState(false);
+  const [isApproveModalVisible, setIsApproveModalVisible] = useState(false);
+
+  const amountInEth = Form.useWatch('amount', form);
+
+  const onIncreaseAmount = async () => {
+    if (!account) return;
+
+    setIsLoading(true);
+
+    const txHash = await updateIncreaseAmount({
+      amount: ethers.parseUnits(`${amountInEth}`, 18).toString(),
+      account,
+    });
+
+    notifySuccess('Amount increased successfully!', `Transaction Hash: ${txHash}`);
+
+    // once the amount is increased, refetch the data
+    refetch();
+
+    closeModal();
+    setIsLoading(false);
+  };
+
+  const onFinish = async ({ amount }: FormValues) => {
+    if (!account) return;
+    if (!veOlasBalance) return;
+
+    try {
+      setIsLoading(true);
+
+      await form.validateFields();
+      const hasSufficientTokens = await hasSufficientTokensRequest({
+        account,
+        amount,
+      });
+
+      if (!hasSufficientTokens) {
+        setIsLoading(false);
+        setIsApproveModalVisible(true);
+        return;
+      }
+
+      await onIncreaseAmount();
+    } catch (error) {
+      window.console.error(error);
+      notifyError();
+    } finally {
+      setIsLoading(false);
+    }
+  };
+
+  /**
+   * can increase amount only if the mapped amount is zero (ie. no lock exists)
+   * or if the user has some olas tokens.
+   */
+  const cannotIncreaseAmount = Number(veOlasBalance) === 0 || Number(olasBalance) === 0 || !account;
+
+  return (
+    <>
+      <Form
+        form={form}
+        layout="vertical"
+        autoComplete="off"
+        name="increase-amount-form"
+        requiredMark={false}
+        onFinish={onFinish}
+      >
+        {getLockedAmountComponent()}
+
+        <Divider className="mt-8" />
+
+        <div className="mb-12">
+          <OlasAmountInput olasBalance={olasBalance} />
+          <MaxButton
+            olasBalance={olasBalance}
+            onMaxClick={() => {
+              form.setFieldsValue({ amount: olasBalance });
+              form.validateFields(['amount']);
+            }}
+          />
+        </div>
+
+        <Divider className="mb-8" />
+
+        <ProjectedVeOlas olasAmount={amountInEth} unlockTime={lockedEnd} />
+
+        <Divider className="mt-8" />
+
+        <Flex gap={12} justify="end" className="mb-4">
+          <Button type="default" onClick={closeModal} size="large">
+            Cancel
+          </Button>
+          <Form.Item className="mb-0">
+            <Button
+              type="primary"
+              htmlType="submit"
+              size="large"
+              disabled={cannotIncreaseAmount}
+              loading={isLoading}
+            >
+              Increase lock
+            </Button>
+          </Form.Item>
+        </Flex>
+      </Form>
+
+      <ApproveOlasModal
+        isModalVisible={isApproveModalVisible}
+        setIsModalVisible={setIsApproveModalVisible}
+        amountInEth={amountInEth}
+        onApprove={onIncreaseAmount}
+      />
+    </>
+  );
+};
diff --git a/apps/govern/components/VeOlas/IncreaseLockModal/IncreaseUnlockTime.tsx b/apps/govern/components/VeOlas/IncreaseLockModal/IncreaseUnlockTime.tsx
new file mode 100644
index 00000000..bbbee2fb
--- /dev/null
+++ b/apps/govern/components/VeOlas/IncreaseLockModal/IncreaseUnlockTime.tsx
@@ -0,0 +1,111 @@
+import { Button, Divider, Flex, Form } from 'antd';
+import { useState } from 'react';
+
+import { notifyError, notifySuccess } from 'libs/util-functions/src';
+
+import {
+  dateInMs,
+  getRemainingTimeInSeconds,
+  updateIncreaseUnlockTime,
+} from 'common-util/functions';
+import { useFetchBalances } from 'hooks/useFetchBalances';
+
+import { ProjectedVeOlas } from '../ProjectedVeOlas';
+import { UnlockTimeInput } from '../UnlockTimeInput';
+import { useVeolasComponents } from '../useVeolasComponents';
+
+type IncreaseUnlockTimeProps = {
+  closeModal: () => void;
+};
+
+type FormValues = {
+  unlockTime: number;
+};
+
+export const IncreaseUnlockTime = ({ closeModal }: IncreaseUnlockTimeProps) => {
+  const [form] = Form.useForm();
+  const { account, lockedEnd, olasBalance, veOlasBalance, refetch } = useFetchBalances();
+  const { getUnlockTimeComponent } = useVeolasComponents();
+
+  const [isLoading, setIsLoading] = useState(false);
+
+  const unlockTime = dateInMs(Form.useWatch('unlockTime', form));
+
+  const onFinish = async ({ unlockTime }: FormValues) => {
+    if (!account) return;
+    if (!veOlasBalance) return;
+
+    try {
+      setIsLoading(true);
+
+      const txHash = await updateIncreaseUnlockTime({
+        time: getRemainingTimeInSeconds(unlockTime),
+        account,
+      });
+
+      notifySuccess('Unlock time increased successfully!', `Transaction Hash: ${txHash}`);
+
+      // once the unlockTime is increased, refetch the data
+      refetch();
+
+      // close the modal after successful locking & loading state
+      closeModal();
+    } catch (error) {
+      window.console.error(error);
+      notifyError();
+    } finally {
+      setIsLoading(false);
+    }
+  };
+
+  /**
+   * can increase amount only if the mapped amount is zero (ie. no lock exists)
+   * or if the user has some olas tokens.
+   */
+  const cannotIncreaseAmount = Number(veOlasBalance) === 0 || Number(olasBalance) === 0 || !account;
+
+  return (
+    <Form
+      form={form}
+      layout="vertical"
+      autoComplete="off"
+      name="increase-unlock-time-form"
+      requiredMark={false}
+      onFinish={onFinish}
+    >
+      {getUnlockTimeComponent()}
+
+      <Divider className="mt-8" />
+
+      <div className="mb-12">
+        <UnlockTimeInput startDate={lockedEnd} />
+      </div>
+
+      <Divider className="mb-8" />
+
+      <ProjectedVeOlas
+        olasAmount={veOlasBalance ? Number(veOlasBalance) : undefined}
+        unlockTime={unlockTime}
+      />
+
+      <Divider className="mt-8" />
+
+      <Flex gap={12} justify="end" className="mb-4">
+        <Button type="default" onClick={closeModal} size="large">
+          Cancel
+        </Button>
+        <Form.Item className="mb-0">
+          <Button
+            type="primary"
+            htmlType="submit"
+            size="large"
+            disabled={cannotIncreaseAmount}
+            loading={isLoading}
+          >
+            Increase lock
+          </Button>
+        </Form.Item>
+      </Flex>
+    </Form>
+  );
+};
diff --git a/apps/govern/components/VeOlas/IncreaseLockModal/index.tsx b/apps/govern/components/VeOlas/IncreaseLockModal/index.tsx
new file mode 100644
index 00000000..2fa4e7ee
--- /dev/null
+++ b/apps/govern/components/VeOlas/IncreaseLockModal/index.tsx
@@ -0,0 +1,44 @@
+import { Modal, Segmented } from 'antd';
+import { useState } from 'react';
+
+import { IncreaseAmount } from './IncreaseAmount';
+import { IncreaseUnlockTime } from './IncreaseUnlockTime';
+
+const TABS = { by_olas_amount: 'By OLAS Amount', by_lock_duration: 'By Lock Duration' };
+const TABS_OPTIONS = Object.values(TABS);
+
+type IncreaseLockModalProps = {
+  isModalVisible: boolean;
+  setIsModalVisible: (value: boolean) => void;
+};
+
+export const IncreaseLockModal = ({
+  isModalVisible,
+  setIsModalVisible,
+}: IncreaseLockModalProps) => {
+  const [selectedTab, setSelectedTab] = useState(TABS_OPTIONS[0]);
+  const handleClose = () => {
+    setIsModalVisible(false);
+  };
+
+  return (
+    <Modal
+      title="Increase Lock"
+      open={isModalVisible}
+      footer={null}
+      onCancel={handleClose}
+      destroyOnClose
+    >
+      <Segmented
+        options={TABS_OPTIONS}
+        value={selectedTab}
+        onChange={setSelectedTab}
+        size="large"
+        className="mt-16"
+      />
+
+      {selectedTab == TABS.by_olas_amount && <IncreaseAmount closeModal={handleClose} />}
+      {selectedTab == TABS.by_lock_duration && <IncreaseUnlockTime closeModal={handleClose} />}
+    </Modal>
+  );
+};
diff --git a/apps/govern/components/VeOlas/InfoCard.tsx b/apps/govern/components/VeOlas/InfoCard.tsx
new file mode 100644
index 00000000..98a389dc
--- /dev/null
+++ b/apps/govern/components/VeOlas/InfoCard.tsx
@@ -0,0 +1,65 @@
+import { Skeleton, Tooltip, Typography } from 'antd';
+import styled from 'styled-components';
+import { useAccount } from 'wagmi';
+
+import { COLOR } from 'libs/ui-theme/src';
+
+const { Title } = Typography;
+
+const InfoCardContainer = styled.div`
+  padding: 16px 0;
+  h5 {
+    font-weight: normal;
+  }
+`;
+
+const ValueText = styled.div`
+  font-size: 30px;
+  font-style: normal;
+  font-weight: 700;
+  line-height: 38px;
+  letter-spacing: -0.9px;
+
+  /* ellipsis */
+  white-space: nowrap;
+  overflow: hidden;
+  text-overflow: ellipsis;
+`;
+
+type InfoCard = {
+  isLoading: boolean;
+  title?: string;
+  value: string;
+  tooltipValue?: string;
+};
+
+const Shimmer = ({ active = true }) => <Skeleton.Input active={active} style={{ maxWidth: 60 }} />;
+
+export const InfoCard = ({ isLoading, title, value, tooltipValue }: InfoCard) => {
+  const { isConnected } = useAccount();
+
+  return (
+    <InfoCardContainer>
+      {title && (
+        <Title level={5} type="secondary" className="mt-0 mb-8">
+          {title}
+        </Title>
+      )}
+
+      <ValueText>
+        {isLoading ? (
+          <Shimmer />
+        ) : (
+          <Tooltip
+            open={tooltipValue ? undefined : false}
+            placement="topLeft"
+            title={isConnected ? tooltipValue : '--'}
+            color={COLOR.BLACK}
+          >
+            {isConnected ? value : '--'}
+          </Tooltip>
+        )}
+      </ValueText>
+    </InfoCardContainer>
+  );
+};
diff --git a/apps/govern/components/VeOlas/MaxButton.tsx b/apps/govern/components/VeOlas/MaxButton.tsx
new file mode 100644
index 00000000..ccc55d30
--- /dev/null
+++ b/apps/govern/components/VeOlas/MaxButton.tsx
@@ -0,0 +1,19 @@
+import { Button, Typography } from 'antd';
+
+import { getCommaSeparatedNumber } from 'common-util/functions';
+
+const { Text } = Typography;
+
+type MaxButtonProps = { olasBalance?: string; onMaxClick: () => void };
+
+export const MaxButton = ({ olasBalance, onMaxClick }: MaxButtonProps) => {
+  return (
+    <Text type="secondary">
+      Balance:&nbsp;
+      {getCommaSeparatedNumber(olasBalance || 0)}&nbsp;OLAS&nbsp;
+      <Button htmlType="button" type="link" onClick={onMaxClick} className="pl-0">
+        Max
+      </Button>
+    </Text>
+  );
+};
diff --git a/apps/govern/components/VeOlas/OlasAmountInput.tsx b/apps/govern/components/VeOlas/OlasAmountInput.tsx
new file mode 100644
index 00000000..2f76187b
--- /dev/null
+++ b/apps/govern/components/VeOlas/OlasAmountInput.tsx
@@ -0,0 +1,36 @@
+import { Form, InputNumber, Typography } from 'antd';
+
+const { Text } = Typography;
+
+type OlasAmountInputProps = {
+  olasBalance?: string;
+};
+/**
+ * @returns Amount Input
+ */
+export const OlasAmountInput = ({ olasBalance }: OlasAmountInputProps) => {
+  return (
+    <Form.Item
+      className="mb-4"
+      name="amount"
+      label={<Text type="secondary">OLAS amount to lock</Text>}
+      rules={[
+        { required: true, message: 'Amount is required' },
+        () => ({
+          validator(_, value) {
+            if (value === '' || value === null) return Promise.resolve();
+            if (value <= 0) {
+              return Promise.reject(new Error('Please input a valid amount'));
+            }
+            if (olasBalance && value > Number(olasBalance)) {
+              return Promise.reject(new Error('Amount cannot be greater than the balance'));
+            }
+            return Promise.resolve();
+          },
+        }),
+      ]}
+    >
+      <InputNumber className="full-width" placeholder="Enter amount" />
+    </Form.Item>
+  );
+};
diff --git a/apps/govern/components/VeOlas/ProjectedVeOlas.tsx b/apps/govern/components/VeOlas/ProjectedVeOlas.tsx
new file mode 100644
index 00000000..f6c78e39
--- /dev/null
+++ b/apps/govern/components/VeOlas/ProjectedVeOlas.tsx
@@ -0,0 +1,41 @@
+import { getCommaSeparatedNumber } from 'common-util/functions';
+
+import { InfoCard } from './InfoCard';
+
+const SECONDS_IN_A_YEAR = 31536000;
+
+type ProjectedVeOlasProps = {
+  olasAmount?: number;
+  unlockTime?: number;
+};
+
+export const ProjectedVeOlas = ({ olasAmount, unlockTime }: ProjectedVeOlasProps) => {
+  /**
+   * @returns projected veOLAS amount as per the formula.
+   * formula = veOLAS = OLAS * lockDuration / maxLockDuration
+   */
+  const getProjectedVeOlas = () => {
+    if (!olasAmount) return 0;
+    if (!unlockTime) return 0;
+
+    const maxLockDuration = SECONDS_IN_A_YEAR * 4;
+    const todayDateMs = new Date().getTime();
+    const lockDuration = (unlockTime - todayDateMs) / 1000;
+
+    const projectedVeOlas = (olasAmount * lockDuration) / maxLockDuration;
+
+    if (!projectedVeOlas || lockDuration < 0) {
+      return 0;
+    }
+
+    return getCommaSeparatedNumber(projectedVeOlas.toFixed(2).toString());
+  };
+
+  return (
+    <InfoCard
+      isLoading={false}
+      title="Estimated voting power you get"
+      value={`${getProjectedVeOlas()} veOLAS`}
+    />
+  );
+};
diff --git a/apps/govern/components/VeOlas/UnlockTimeInput.tsx b/apps/govern/components/VeOlas/UnlockTimeInput.tsx
new file mode 100644
index 00000000..3fb220a0
--- /dev/null
+++ b/apps/govern/components/VeOlas/UnlockTimeInput.tsx
@@ -0,0 +1,62 @@
+import { DatePicker, DatePickerProps, Form, Typography } from 'antd';
+import range from 'lodash/range';
+import React from 'react';
+
+const { Text } = Typography;
+
+type UnlockTimeInputProps = {
+  startDate?: number;
+};
+
+// Helper function to add days to a date
+const addDays = (date: Date, days: number) => {
+  const result = new Date(date);
+  result.setDate(result.getDate() + days);
+  return result;
+};
+
+export const UnlockTimeInput = ({ startDate }: UnlockTimeInputProps) => {
+  const tempStartDate = startDate ? new Date(startDate) : new Date();
+
+  // Function to disable specific dates
+  const disableDateForUnlockTime: DatePickerProps['disabledDate'] = (current) => {
+    const currentDate = current.toDate();
+    const today = new Date();
+    const sevenDaysFromTempStartDate = addDays(tempStartDate, 6);
+    const fourYearsFromToday = addDays(today, 4 * 365);
+
+    const pastDate = currentDate < sevenDaysFromTempStartDate;
+    const notSameDayInFuture = currentDate.getDay() !== today.getDay();
+    const futureDate = currentDate > fourYearsFromToday;
+
+    return pastDate || notSameDayInFuture || futureDate;
+  };
+
+  return (
+    <Form.Item
+      name="unlockTime"
+      label={<Text type="secondary">Unlock date and time</Text>}
+      rules={[{ required: true, message: 'Unlock Time is required' }]}
+      tooltip="The date should be minimum 1 week and maximum 4 years"
+      className="mb-4"
+    >
+      <DatePicker
+        disabledDate={disableDateForUnlockTime}
+        disabledTime={() => {
+          const now = new Date();
+          const currentHour = now.getHours();
+          const currentMinute = now.getMinutes();
+
+          return {
+            disabledHours: () => range(0, currentHour),
+            disabledMinutes: () => range(0, currentMinute),
+          };
+        }}
+        format="MM/DD/YYYY HH:mm"
+        className="full-width"
+        showTime={{ format: 'HH:mm' }}
+        size="large"
+      />
+    </Form.Item>
+  );
+};
diff --git a/apps/govern/components/VeOlas/VeOlasManage.tsx b/apps/govern/components/VeOlas/VeOlasManage.tsx
new file mode 100644
index 00000000..15581e2b
--- /dev/null
+++ b/apps/govern/components/VeOlas/VeOlasManage.tsx
@@ -0,0 +1,68 @@
+import { Button, Col, Row } from 'antd';
+import { useState } from 'react';
+
+import { notifySuccess } from '@autonolas/frontend-library';
+
+import { notifyError } from 'libs/util-functions/src';
+
+import { withdrawVeolasRequest } from 'common-util/functions';
+import { useFetchBalances } from 'hooks/index';
+
+import { useVeolasComponents } from './useVeolasComponents';
+
+export const VeOlasManage = () => {
+  const { isLoading, canWithdrawVeolas, account, refetch } = useFetchBalances();
+  const {
+    getBalanceComponent,
+    getVotingPowerComponent,
+    getVotingPercentComponent,
+    getLockedAmountComponent,
+    getUnlockTimeComponent,
+    getUnlockedAmountComponent,
+  } = useVeolasComponents();
+
+  const [isWithdrawLoading, setIsWithdrawLoading] = useState(false);
+
+  const onWithdraw = async () => {
+    if (!account) return;
+
+    try {
+      setIsWithdrawLoading(true);
+      await withdrawVeolasRequest({ account });
+      notifySuccess('Claimed successfully');
+
+      refetch();
+    } catch (error) {
+      window.console.error(error);
+      notifyError();
+    } finally {
+      setIsWithdrawLoading(false);
+    }
+  };
+
+  return (
+    <>
+      <Row>
+        <Col span={8}>{getBalanceComponent()}</Col>
+
+        <Col span={8}>{getVotingPowerComponent()}</Col>
+
+        <Col span={8}>{getVotingPercentComponent()}</Col>
+
+        <Col span={8}>{getLockedAmountComponent()}</Col>
+
+        <Col span={8}>
+          {getUnlockTimeComponent()}
+
+          {!isLoading && canWithdrawVeolas && (
+            <Button htmlType="submit" onClick={onWithdraw} loading={isWithdrawLoading}>
+              Claim all
+            </Button>
+          )}
+        </Col>
+
+        <Col span={8}>{getUnlockedAmountComponent()}</Col>
+      </Row>
+    </>
+  );
+};
diff --git a/apps/govern/components/VeOlas/index.tsx b/apps/govern/components/VeOlas/index.tsx
new file mode 100644
index 00000000..0445c29c
--- /dev/null
+++ b/apps/govern/components/VeOlas/index.tsx
@@ -0,0 +1,75 @@
+import { Alert, Button, Card, Space, Typography } from 'antd';
+import { useState } from 'react';
+import styled from 'styled-components';
+
+import { useFetchBalances } from 'hooks/index';
+
+import { CreateLockModal } from './CreateLockModal';
+import { IncreaseLockModal } from './IncreaseLockModal';
+import { VeOlasManage } from './VeOlasManage';
+
+const { Paragraph } = Typography;
+
+const StyledMain = styled.main`
+  display: flex;
+  flex-direction: column;
+  max-width: 946px;
+  margin: 0 auto;
+`;
+
+const Title = styled.h1`
+  font-size: 24px;
+  margin: 0 0 8px;
+`;
+
+export const VeOlasPage = () => {
+  const { isLoading, canWithdrawVeolas, canIncreaseAmountOrUnlock } = useFetchBalances();
+
+  const [isCreateLockModalVisible, setIsCreateLockModalVisible] = useState(false);
+  const [isIncreaseModalVisible, setIsIncreaseModalVisible] = useState(false);
+
+  return (
+    <StyledMain>
+      <Card>
+        <Title>veOLAS</Title>
+        <Paragraph type="secondary" className="mt-8 mb-24">
+          veOLAS gives you voting power in Olas governance. Lock OLAS for longer periods to get more
+          veOLAS.
+        </Paragraph>
+        <Space size="middle" className="mb-16">
+          <Button
+            type="primary"
+            size="large"
+            disabled={isLoading || !!canWithdrawVeolas}
+            onClick={() => {
+              // if the user has veolas, then show the modal to increase the amount
+              // else show the modal to create a lock
+              if (canIncreaseAmountOrUnlock) {
+                setIsIncreaseModalVisible(true);
+              } else {
+                setIsCreateLockModalVisible(true);
+              }
+            }}
+          >
+            Lock OLAS for veOLAS
+          </Button>
+
+          {canWithdrawVeolas && (
+            <Alert message="Please claim your OLAS before locking again" type="warning" showIcon />
+          )}
+        </Space>
+
+        <VeOlasManage />
+
+        <CreateLockModal
+          isModalVisible={isCreateLockModalVisible}
+          setIsModalVisible={setIsCreateLockModalVisible}
+        />
+        <IncreaseLockModal
+          isModalVisible={isIncreaseModalVisible}
+          setIsModalVisible={setIsIncreaseModalVisible}
+        />
+      </Card>
+    </StyledMain>
+  );
+};
diff --git a/apps/govern/components/VeOlas/useVeolasComponents.tsx b/apps/govern/components/VeOlas/useVeolasComponents.tsx
new file mode 100644
index 00000000..29480d53
--- /dev/null
+++ b/apps/govern/components/VeOlas/useVeolasComponents.tsx
@@ -0,0 +1,107 @@
+import {
+  formatWeiNumber,
+  getCommaSeparatedNumber,
+  getFormattedDate,
+  getFullFormattedDate,
+} from 'common-util/functions';
+import { useFetchBalances } from 'hooks/useFetchBalances';
+
+import { InfoCard } from './InfoCard';
+
+const getTotalVotesPercentage = (
+  votingPower: string | undefined,
+  totalSupply: string | undefined,
+) => {
+  if (votingPower && totalSupply) {
+    const votingPowerInPercentage = ((Number(votingPower) / Number(totalSupply)) * 100).toFixed(2);
+    return formatWeiNumber({ value: votingPowerInPercentage });
+  }
+
+  return null;
+};
+
+/**
+ * This hook is used to get the components
+ */
+export const useVeolasComponents = () => {
+  const {
+    isLoading,
+    olasBalance,
+    veOlasBalance,
+    votingPower,
+    totalSupply,
+    lockedEnd,
+    canWithdrawVeolas,
+  } = useFetchBalances();
+
+  const getBalanceComponent = () => (
+    <InfoCard
+      isLoading={isLoading}
+      title="OLAS balance"
+      value={formatWeiNumber({ value: olasBalance, maximumFractionDigits: 3 })}
+      tooltipValue={getCommaSeparatedNumber(olasBalance)}
+    />
+  );
+
+  const getVotingPowerComponent = () => (
+    <InfoCard
+      isLoading={isLoading}
+      title="Voting power"
+      value={formatWeiNumber({ value: votingPower })}
+      tooltipValue={getCommaSeparatedNumber(votingPower)}
+    />
+  );
+
+  const getVotingPercentComponent = () => (
+    <InfoCard
+      isLoading={isLoading}
+      title="% of total voting power"
+      value={
+        Number(votingPower) === 0 || Number(totalSupply) === 0
+          ? '0%'
+          : `${getTotalVotesPercentage(votingPower, totalSupply)}%`
+      }
+    />
+  );
+
+  const getLockedAmountComponent = () => (
+    <InfoCard
+      isLoading={isLoading}
+      title="Current locked OLAS"
+      value={formatWeiNumber({ value: veOlasBalance, maximumFractionDigits: 3 })}
+      tooltipValue={getCommaSeparatedNumber(veOlasBalance)}
+    />
+  );
+
+  const getUnlockTimeComponent = () => (
+    <InfoCard
+      isLoading={isLoading}
+      title="Current unlock date"
+      value={getFormattedDate(Number(lockedEnd))}
+      tooltipValue={getFullFormattedDate(Number(lockedEnd))}
+    />
+  );
+
+  // unlocked OLAS = balanceOf(amount) of veOlas contract
+  const getUnlockedAmountComponent = () => {
+    // if the user has no locked OLAS, then don't show the component
+    if (!canWithdrawVeolas) return null;
+    return (
+      <InfoCard
+        isLoading={isLoading}
+        title="Unlocked OLAS"
+        value={formatWeiNumber({ value: veOlasBalance, maximumFractionDigits: 3 })}
+        tooltipValue={getCommaSeparatedNumber(veOlasBalance)}
+      />
+    );
+  };
+
+  return {
+    getBalanceComponent,
+    getVotingPowerComponent,
+    getVotingPercentComponent,
+    getLockedAmountComponent,
+    getUnlockTimeComponent,
+    getUnlockedAmountComponent,
+  };
+};
diff --git a/apps/govern/hooks/index.ts b/apps/govern/hooks/index.ts
index adf03d78..0076b892 100644
--- a/apps/govern/hooks/index.ts
+++ b/apps/govern/hooks/index.ts
@@ -1,4 +1,5 @@
 export * from './useFetchStakingContractsList';
 export * from './useFetchUserVotes';
+export * from './useFetchBalances';
 export * from './useVotingPower';
 export * from './useContractParams';
diff --git a/apps/govern/hooks/useFetchBalances.ts b/apps/govern/hooks/useFetchBalances.ts
new file mode 100644
index 00000000..da680f16
--- /dev/null
+++ b/apps/govern/hooks/useFetchBalances.ts
@@ -0,0 +1,105 @@
+import { ethers } from 'ethers';
+import { useMemo } from 'react';
+import { Address } from 'viem';
+import { mainnet } from 'viem/chains';
+import { useAccount, useBlock, useReadContracts } from 'wagmi';
+
+import { OLAS, VE_OLAS } from 'libs/util-contracts/src/lib/abiAndAddresses';
+
+import { LATEST_BLOCK_KEY } from 'common-util/constants/scopeKeys';
+
+import { useVotingPower } from './useVotingPower';
+
+const getContracts = (account: Address) => [
+  {
+    address: OLAS.addresses[mainnet.id],
+    abi: OLAS.abi,
+    chainId: mainnet.id,
+    functionName: 'balanceOf',
+    args: [account],
+  },
+  {
+    address: VE_OLAS.addresses[mainnet.id],
+    abi: VE_OLAS.abi,
+    chainId: mainnet.id,
+    functionName: 'totalSupplyLocked',
+  },
+  {
+    address: VE_OLAS.addresses[mainnet.id],
+    abi: VE_OLAS.abi,
+    chainId: mainnet.id,
+    functionName: 'mapLockedBalances',
+    args: [account],
+  },
+];
+
+export const useFetchBalances = () => {
+  const { address: account } = useAccount();
+  const {
+    data: votingPower,
+    isFetching: isVotingPowerFetching,
+    refetch: refetchVotingPower,
+  } = useVotingPower(account);
+
+  const {
+    data: block,
+    isFetching: isBlockFetching,
+    refetch: refetchBlock,
+  } = useBlock({
+    blockTag: 'latest',
+    scopeKey: LATEST_BLOCK_KEY,
+  });
+
+  const {
+    data: balanceData,
+    isFetching: isBalanceFetching,
+    refetch: refetchBalances,
+  } = useReadContracts({
+    contracts: getContracts(account || '0x'),
+    query: {
+      enabled: !!account,
+      select: (data) => {
+        const [olasBalanceData, totalSupplyLockedData, mapLockedBalancesData] = data;
+        const [veOlasBalance, lockedEnd] = mapLockedBalancesData.result as bigint[];
+
+        return {
+          olasBalance: ethers.formatUnits(olasBalanceData.result as bigint, 18),
+          veOlasBalance: ethers.formatUnits(veOlasBalance as bigint, 18),
+          totalSupplyLocked: ethers.formatUnits(totalSupplyLockedData.result as bigint, 18),
+          lockedEnd: Number(lockedEnd) * 1000,
+        };
+      },
+    },
+  });
+
+  const canWithdrawVeolas = useMemo(() => {
+    if (balanceData === undefined) return false;
+    if (block === undefined) return false;
+
+    return Number(balanceData.veOlasBalance) > 0 && balanceData.lockedEnd <= block.timestamp;
+  }, [balanceData, block]);
+
+  const refetch = async () => {
+    try {
+      await refetchVotingPower();
+      await refetchBlock();
+      await refetchBalances();
+    } catch (error) {
+      return Promise.reject(error);
+    }
+    return Promise.resolve();
+  };
+
+  return {
+    isLoading: isVotingPowerFetching || isBalanceFetching || isBlockFetching,
+    account,
+    votingPower,
+    totalSupply: balanceData?.totalSupplyLocked,
+    olasBalance: balanceData?.olasBalance,
+    veOlasBalance: balanceData?.veOlasBalance,
+    lockedEnd: balanceData?.lockedEnd,
+    canWithdrawVeolas,
+    canIncreaseAmountOrUnlock: balanceData ? Number(balanceData.veOlasBalance) > 0 : false,
+    refetch,
+  };
+};
diff --git a/apps/govern/hooks/useVotingPower.ts b/apps/govern/hooks/useVotingPower.ts
index 049fbf53..fa11c415 100644
--- a/apps/govern/hooks/useVotingPower.ts
+++ b/apps/govern/hooks/useVotingPower.ts
@@ -6,7 +6,7 @@ import { useReadContract } from 'wagmi';
 import { VE_OLAS } from 'libs/util-contracts/src/lib/abiAndAddresses';
 
 export const useVotingPower = (account: Address | undefined) => {
-  const { data, isFetching } = useReadContract({
+  const { data, isFetching, refetch } = useReadContract({
     address: (VE_OLAS.addresses as Record<number, Address>)[mainnet.id],
     abi: VE_OLAS.abi,
     functionName: 'getVotes',
@@ -18,5 +18,5 @@ export const useVotingPower = (account: Address | undefined) => {
     },
   });
 
-  return { data, isFetching };
+  return { data, isFetching, refetch };
 };
diff --git a/apps/govern/pages/veolas.tsx b/apps/govern/pages/veolas.tsx
new file mode 100644
index 00000000..08219922
--- /dev/null
+++ b/apps/govern/pages/veolas.tsx
@@ -0,0 +1,3 @@
+import { VeOlasPage } from '../components/VeOlas';
+
+export default VeOlasPage;
diff --git a/libs/ui-theme/src/lib/GlobalStyles.tsx b/libs/ui-theme/src/lib/GlobalStyles.tsx
index 082a6b14..ff03a500 100644
--- a/libs/ui-theme/src/lib/GlobalStyles.tsx
+++ b/libs/ui-theme/src/lib/GlobalStyles.tsx
@@ -106,6 +106,10 @@ export const GlobalStyles = createGlobalStyle`
     padding-top: 48px;
   }
 
+  .block {
+    display: block;
+  }
+
   .text-start {
     text-align: start;
   }
diff --git a/libs/ui-theme/src/lib/ThemeConfig.tsx b/libs/ui-theme/src/lib/ThemeConfig.tsx
index c08f086a..89773073 100644
--- a/libs/ui-theme/src/lib/ThemeConfig.tsx
+++ b/libs/ui-theme/src/lib/ThemeConfig.tsx
@@ -85,6 +85,9 @@ export const THEME_CONFIG: ThemeConfig = {
     Collapse: {
       colorBorder: COLOR.BORDER_GREY_2,
     },
+    Modal: {
+      titleFontSize: 24,
+    },
   },
 };
 
diff --git a/libs/util-constants/src/lib/symbols.ts b/libs/util-constants/src/lib/symbols.ts
index 37cd357a..bb53f5d3 100644
--- a/libs/util-constants/src/lib/symbols.ts
+++ b/libs/util-constants/src/lib/symbols.ts
@@ -1,3 +1,5 @@
 export const UNICODE_SYMBOLS = {
   EXTERNAL_LINK: '↗',
 };
+
+export const NA = 'n/a';
diff --git a/libs/util-contracts/src/lib/abiAndAddresses/index.js b/libs/util-contracts/src/lib/abiAndAddresses/index.js
index 5ed75cdb..89826df8 100644
--- a/libs/util-contracts/src/lib/abiAndAddresses/index.js
+++ b/libs/util-contracts/src/lib/abiAndAddresses/index.js
@@ -15,3 +15,4 @@ export * from './stakingFactory';
 export * from './veOlas';
 export * from './stakingToken';
 export * from './stakingVerifier';
+export * from './olas';
diff --git a/libs/util-contracts/src/lib/abiAndAddresses/olas.ts b/libs/util-contracts/src/lib/abiAndAddresses/olas.ts
new file mode 100644
index 00000000..a5394f35
--- /dev/null
+++ b/libs/util-contracts/src/lib/abiAndAddresses/olas.ts
@@ -0,0 +1,575 @@
+import { Contract } from './types';
+
+export const OLAS: Contract = {
+  contractName: 'OLAS',
+  addresses: {
+    1: '0x0001A500A6B18995B03f44bb040A5fFc28E45CB0',
+    5: '0xEdfc28215B1Eb6eb0be426f1f529cf691A5C2400',
+  },
+  abi: [
+    {
+      inputs: [],
+      stateMutability: 'nonpayable',
+      type: 'constructor',
+    },
+    {
+      inputs: [
+        {
+          internalType: 'address',
+          name: 'sender',
+          type: 'address',
+        },
+        {
+          internalType: 'address',
+          name: 'manager',
+          type: 'address',
+        },
+      ],
+      name: 'ManagerOnly',
+      type: 'error',
+    },
+    {
+      inputs: [],
+      name: 'ZeroAddress',
+      type: 'error',
+    },
+    {
+      anonymous: false,
+      inputs: [
+        {
+          indexed: true,
+          internalType: 'address',
+          name: 'owner',
+          type: 'address',
+        },
+        {
+          indexed: true,
+          internalType: 'address',
+          name: 'spender',
+          type: 'address',
+        },
+        {
+          indexed: false,
+          internalType: 'uint256',
+          name: 'amount',
+          type: 'uint256',
+        },
+      ],
+      name: 'Approval',
+      type: 'event',
+    },
+    {
+      anonymous: false,
+      inputs: [
+        {
+          indexed: true,
+          internalType: 'address',
+          name: 'minter',
+          type: 'address',
+        },
+      ],
+      name: 'MinterUpdated',
+      type: 'event',
+    },
+    {
+      anonymous: false,
+      inputs: [
+        {
+          indexed: true,
+          internalType: 'address',
+          name: 'owner',
+          type: 'address',
+        },
+      ],
+      name: 'OwnerUpdated',
+      type: 'event',
+    },
+    {
+      anonymous: false,
+      inputs: [
+        {
+          indexed: true,
+          internalType: 'address',
+          name: 'from',
+          type: 'address',
+        },
+        {
+          indexed: true,
+          internalType: 'address',
+          name: 'to',
+          type: 'address',
+        },
+        {
+          indexed: false,
+          internalType: 'uint256',
+          name: 'amount',
+          type: 'uint256',
+        },
+      ],
+      name: 'Transfer',
+      type: 'event',
+    },
+    {
+      inputs: [],
+      name: 'DOMAIN_SEPARATOR',
+      outputs: [
+        {
+          internalType: 'bytes32',
+          name: '',
+          type: 'bytes32',
+        },
+      ],
+      stateMutability: 'view',
+      type: 'function',
+    },
+    {
+      inputs: [
+        {
+          internalType: 'address',
+          name: '',
+          type: 'address',
+        },
+        {
+          internalType: 'address',
+          name: '',
+          type: 'address',
+        },
+      ],
+      name: 'allowance',
+      outputs: [
+        {
+          internalType: 'uint256',
+          name: '',
+          type: 'uint256',
+        },
+      ],
+      stateMutability: 'view',
+      type: 'function',
+    },
+    {
+      inputs: [
+        {
+          internalType: 'address',
+          name: 'spender',
+          type: 'address',
+        },
+        {
+          internalType: 'uint256',
+          name: 'amount',
+          type: 'uint256',
+        },
+      ],
+      name: 'approve',
+      outputs: [
+        {
+          internalType: 'bool',
+          name: '',
+          type: 'bool',
+        },
+      ],
+      stateMutability: 'nonpayable',
+      type: 'function',
+    },
+    {
+      inputs: [
+        {
+          internalType: 'address',
+          name: '',
+          type: 'address',
+        },
+      ],
+      name: 'balanceOf',
+      outputs: [
+        {
+          internalType: 'uint256',
+          name: '',
+          type: 'uint256',
+        },
+      ],
+      stateMutability: 'view',
+      type: 'function',
+    },
+    {
+      inputs: [
+        {
+          internalType: 'uint256',
+          name: 'amount',
+          type: 'uint256',
+        },
+      ],
+      name: 'burn',
+      outputs: [],
+      stateMutability: 'nonpayable',
+      type: 'function',
+    },
+    {
+      inputs: [
+        {
+          internalType: 'address',
+          name: 'newMinter',
+          type: 'address',
+        },
+      ],
+      name: 'changeMinter',
+      outputs: [],
+      stateMutability: 'nonpayable',
+      type: 'function',
+    },
+    {
+      inputs: [
+        {
+          internalType: 'address',
+          name: 'newOwner',
+          type: 'address',
+        },
+      ],
+      name: 'changeOwner',
+      outputs: [],
+      stateMutability: 'nonpayable',
+      type: 'function',
+    },
+    {
+      inputs: [],
+      name: 'decimals',
+      outputs: [
+        {
+          internalType: 'uint8',
+          name: '',
+          type: 'uint8',
+        },
+      ],
+      stateMutability: 'view',
+      type: 'function',
+    },
+    {
+      inputs: [
+        {
+          internalType: 'address',
+          name: 'spender',
+          type: 'address',
+        },
+        {
+          internalType: 'uint256',
+          name: 'amount',
+          type: 'uint256',
+        },
+      ],
+      name: 'decreaseAllowance',
+      outputs: [
+        {
+          internalType: 'bool',
+          name: '',
+          type: 'bool',
+        },
+      ],
+      stateMutability: 'nonpayable',
+      type: 'function',
+    },
+    {
+      inputs: [
+        {
+          internalType: 'address',
+          name: 'spender',
+          type: 'address',
+        },
+        {
+          internalType: 'uint256',
+          name: 'amount',
+          type: 'uint256',
+        },
+      ],
+      name: 'increaseAllowance',
+      outputs: [
+        {
+          internalType: 'bool',
+          name: '',
+          type: 'bool',
+        },
+      ],
+      stateMutability: 'nonpayable',
+      type: 'function',
+    },
+    {
+      inputs: [
+        {
+          internalType: 'uint256',
+          name: 'amount',
+          type: 'uint256',
+        },
+      ],
+      name: 'inflationControl',
+      outputs: [
+        {
+          internalType: 'bool',
+          name: '',
+          type: 'bool',
+        },
+      ],
+      stateMutability: 'view',
+      type: 'function',
+    },
+    {
+      inputs: [],
+      name: 'inflationRemainder',
+      outputs: [
+        {
+          internalType: 'uint256',
+          name: 'remainder',
+          type: 'uint256',
+        },
+      ],
+      stateMutability: 'view',
+      type: 'function',
+    },
+    {
+      inputs: [],
+      name: 'maxMintCapFraction',
+      outputs: [
+        {
+          internalType: 'uint256',
+          name: '',
+          type: 'uint256',
+        },
+      ],
+      stateMutability: 'view',
+      type: 'function',
+    },
+    {
+      inputs: [
+        {
+          internalType: 'address',
+          name: 'account',
+          type: 'address',
+        },
+        {
+          internalType: 'uint256',
+          name: 'amount',
+          type: 'uint256',
+        },
+      ],
+      name: 'mint',
+      outputs: [],
+      stateMutability: 'nonpayable',
+      type: 'function',
+    },
+    {
+      inputs: [],
+      name: 'minter',
+      outputs: [
+        {
+          internalType: 'address',
+          name: '',
+          type: 'address',
+        },
+      ],
+      stateMutability: 'view',
+      type: 'function',
+    },
+    {
+      inputs: [],
+      name: 'name',
+      outputs: [
+        {
+          internalType: 'string',
+          name: '',
+          type: 'string',
+        },
+      ],
+      stateMutability: 'view',
+      type: 'function',
+    },
+    {
+      inputs: [
+        {
+          internalType: 'address',
+          name: '',
+          type: 'address',
+        },
+      ],
+      name: 'nonces',
+      outputs: [
+        {
+          internalType: 'uint256',
+          name: '',
+          type: 'uint256',
+        },
+      ],
+      stateMutability: 'view',
+      type: 'function',
+    },
+    {
+      inputs: [],
+      name: 'oneYear',
+      outputs: [
+        {
+          internalType: 'uint256',
+          name: '',
+          type: 'uint256',
+        },
+      ],
+      stateMutability: 'view',
+      type: 'function',
+    },
+    {
+      inputs: [],
+      name: 'owner',
+      outputs: [
+        {
+          internalType: 'address',
+          name: '',
+          type: 'address',
+        },
+      ],
+      stateMutability: 'view',
+      type: 'function',
+    },
+    {
+      inputs: [
+        {
+          internalType: 'address',
+          name: 'owner',
+          type: 'address',
+        },
+        {
+          internalType: 'address',
+          name: 'spender',
+          type: 'address',
+        },
+        {
+          internalType: 'uint256',
+          name: 'value',
+          type: 'uint256',
+        },
+        {
+          internalType: 'uint256',
+          name: 'deadline',
+          type: 'uint256',
+        },
+        {
+          internalType: 'uint8',
+          name: 'v',
+          type: 'uint8',
+        },
+        {
+          internalType: 'bytes32',
+          name: 'r',
+          type: 'bytes32',
+        },
+        {
+          internalType: 'bytes32',
+          name: 's',
+          type: 'bytes32',
+        },
+      ],
+      name: 'permit',
+      outputs: [],
+      stateMutability: 'nonpayable',
+      type: 'function',
+    },
+    {
+      inputs: [],
+      name: 'symbol',
+      outputs: [
+        {
+          internalType: 'string',
+          name: '',
+          type: 'string',
+        },
+      ],
+      stateMutability: 'view',
+      type: 'function',
+    },
+    {
+      inputs: [],
+      name: 'tenYearSupplyCap',
+      outputs: [
+        {
+          internalType: 'uint256',
+          name: '',
+          type: 'uint256',
+        },
+      ],
+      stateMutability: 'view',
+      type: 'function',
+    },
+    {
+      inputs: [],
+      name: 'timeLaunch',
+      outputs: [
+        {
+          internalType: 'uint256',
+          name: '',
+          type: 'uint256',
+        },
+      ],
+      stateMutability: 'view',
+      type: 'function',
+    },
+    {
+      inputs: [],
+      name: 'totalSupply',
+      outputs: [
+        {
+          internalType: 'uint256',
+          name: '',
+          type: 'uint256',
+        },
+      ],
+      stateMutability: 'view',
+      type: 'function',
+    },
+    {
+      inputs: [
+        {
+          internalType: 'address',
+          name: 'to',
+          type: 'address',
+        },
+        {
+          internalType: 'uint256',
+          name: 'amount',
+          type: 'uint256',
+        },
+      ],
+      name: 'transfer',
+      outputs: [
+        {
+          internalType: 'bool',
+          name: '',
+          type: 'bool',
+        },
+      ],
+      stateMutability: 'nonpayable',
+      type: 'function',
+    },
+    {
+      inputs: [
+        {
+          internalType: 'address',
+          name: 'from',
+          type: 'address',
+        },
+        {
+          internalType: 'address',
+          name: 'to',
+          type: 'address',
+        },
+        {
+          internalType: 'uint256',
+          name: 'amount',
+          type: 'uint256',
+        },
+      ],
+      name: 'transferFrom',
+      outputs: [
+        {
+          internalType: 'bool',
+          name: '',
+          type: 'bool',
+        },
+      ],
+      stateMutability: 'nonpayable',
+      type: 'function',
+    },
+  ],
+};
diff --git a/libs/util-contracts/src/lib/abiAndAddresses/types.ts b/libs/util-contracts/src/lib/abiAndAddresses/types.ts
new file mode 100644
index 00000000..0f1e2843
--- /dev/null
+++ b/libs/util-contracts/src/lib/abiAndAddresses/types.ts
@@ -0,0 +1,7 @@
+import { Abi, Address } from 'viem';
+
+export type Contract = {
+  contractName: string;
+  addresses: Record<number, Address>;
+  abi: Abi;
+};
diff --git a/libs/util-contracts/src/lib/abiAndAddresses/veOlas.js b/libs/util-contracts/src/lib/abiAndAddresses/veOlas.ts
similarity index 99%
rename from libs/util-contracts/src/lib/abiAndAddresses/veOlas.js
rename to libs/util-contracts/src/lib/abiAndAddresses/veOlas.ts
index 332a04c8..c9b78b01 100644
--- a/libs/util-contracts/src/lib/abiAndAddresses/veOlas.js
+++ b/libs/util-contracts/src/lib/abiAndAddresses/veOlas.ts
@@ -1,4 +1,6 @@
-export const VE_OLAS = {
+import { Contract } from './types';
+
+export const VE_OLAS: Contract = {
   contractName: 'veOLAS',
   addresses: {
     1: '0x7e01A500805f8A52Fad229b3015AD130A332B7b3',