Skip to content

Commit

Permalink
feat(rwa): start with gaspayable status bar (#2792)
Browse files Browse the repository at this point in the history
  • Loading branch information
sstraatemans authored Jan 16, 2025
1 parent ec2d7d9 commit 337ea28
Show file tree
Hide file tree
Showing 9 changed files with 410 additions and 2 deletions.
2 changes: 2 additions & 0 deletions packages/apps/rwa-demo/src/app/(app)/layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import { ActiveTransactionsList } from '@/components/ActiveTransactionsList/Acti
import { AssetInfo } from '@/components/AssetInfo/AssetInfo';
import { DemoBanner } from '@/components/DemoBanner/DemoBanner';
import { FrozenInvestorBanner } from '@/components/FrozenInvestorBanner/FrozenInvestorBanner';
import { GasPayableBanner } from '@/components/GasPayableBanner/GasPayableBanner';
import { GraphOnlineBanner } from '@/components/GraphOnlineBanner/GraphOnlineBanner';
import { TransactionPendingIcon } from '@/components/TransactionPendingIcon/TransactionPendingIcon';
import { useAccount } from '@/hooks/account';
Expand Down Expand Up @@ -94,6 +95,7 @@ const RootLayout = ({
<DemoBanner />
<GraphOnlineBanner />
<FrozenInvestorBanner />
<GasPayableBanner />
</Stack>
}
logo={
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
import type { IAgentHookProps } from '@/hooks/getAgentRoles';
import { useGetAgentRoles } from '@/hooks/getAgentRoles';
import { useGetInvestorBalance } from '@/hooks/getInvestorBalance';
import { accountKDABalance } from '@/services/accountKDABalance';
import { isAgent } from '@/services/isAgent';
import { isComplianceOwner } from '@/services/isComplianceOwner';
import { isFrozen } from '@/services/isFrozen';
Expand Down Expand Up @@ -36,6 +37,7 @@ export interface IAccountContext {
selectAccount: (account: IWalletAccount) => void;
balance: number;
accountRoles: IAgentHookProps;
isGasPayable: boolean;
}

export const AccountContext = createContext<IAccountContext>({
Expand All @@ -61,6 +63,7 @@ export const AccountContext = createContext<IAccountContext>({
isFreezer: () => false,
isTransferManager: () => false,
},
isGasPayable: false,
});

export const AccountProvider: FC<PropsWithChildren> = ({ children }) => {
Expand All @@ -72,6 +75,7 @@ export const AccountProvider: FC<PropsWithChildren> = ({ children }) => {
const [isAgentState, setIsAgentState] = useState(false);
const [isInvestorState, setIsInvestorState] = useState(false);
const [isFrozenState, setIsFrozenState] = useState(false);
const [kdaBalance, setKdaBalance] = useState(-1);
const { ...accountRoles } = useGetAgentRoles({
agent: account?.address,
});
Expand All @@ -84,6 +88,14 @@ export const AccountProvider: FC<PropsWithChildren> = ({ children }) => {
const resIsAgent = await isAgent({ agent: account.address });
setIsAgentState(!!resIsAgent);
};
const checkIsGasPayable = async (account: IWalletAccount) => {
const res = await accountKDABalance(
{ accountName: account.address },
account,
);

setKdaBalance(res);
};
const checkIsOwner = async (account: IWalletAccount) => {
const resIsOwner = await isOwner({ owner: account.address });
setIsOwnerState(!!resIsOwner);
Expand Down Expand Up @@ -164,10 +176,10 @@ export const AccountProvider: FC<PropsWithChildren> = ({ children }) => {
}, []);

useEffect(() => {
if (accountRoles.isMounted) {
if (accountRoles.isMounted && kdaBalance > -1) {
setIsMounted(true);
}
}, [accountRoles.isMounted]);
}, [accountRoles.isMounted, kdaBalance]);

useEffect(() => {
if (!account) {
Expand All @@ -178,6 +190,8 @@ export const AccountProvider: FC<PropsWithChildren> = ({ children }) => {
return;
}

// eslint-disable-next-line @typescript-eslint/no-floating-promises
checkIsGasPayable(account);
// eslint-disable-next-line @typescript-eslint/no-floating-promises
checkIsOwner(account);
// eslint-disable-next-line @typescript-eslint/no-floating-promises
Expand Down Expand Up @@ -219,6 +233,7 @@ export const AccountProvider: FC<PropsWithChildren> = ({ children }) => {
selectAccount,
balance,
accountRoles,
isGasPayable: kdaBalance > 0,
}}
>
{children}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
import { useFaucet } from '@/hooks/faucet';
import { env } from '@/utils/env';
import { MonoMonetizationOn } from '@kadena/kode-icons';
import {
Button,
Notification,
NotificationFooter,
NotificationHeading,
} from '@kadena/kode-ui';
import { useNotifications } from '@kadena/kode-ui/patterns';
import type { FC } from 'react';
import { useState } from 'react';
import { TransactionTypeSpinner } from '../TransactionTypeSpinner/TransactionTypeSpinner';
import { TXTYPES } from '../TransactionsProvider/TransactionsProvider';

export const GasPayableBanner: FC = () => {
const { submit, isAllowed } = useFaucet();
const [isDone, setIsDone] = useState(false);
const { addNotification } = useNotifications();

const handleAddKda = async () => {
const tx = await submit();
tx?.listener.subscribe(
() => {},
() => {},
() => {
addNotification({
intent: 'positive',
label: 'KDA added to account',
message: `We added ${env.FAUCETAMOUNT} KDA to the account`,
});

setIsDone(true);
},
);
};

if (!isAllowed || isDone) return null;

return (
<Notification intent="warning" role="status" type="stacked">
<NotificationHeading>
The account has no balance to pay the gas
</NotificationHeading>
<NotificationFooter>
<Button
isDisabled={!isAllowed}
onPress={handleAddKda}
variant="warning"
endVisual={
<TransactionTypeSpinner
type={TXTYPES.FAUCET}
fallbackIcon={<MonoMonetizationOn />}
/>
}
>
Add 5 KDA for Gas
</Button>
</NotificationFooter>
</Notification>
);
};
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ export const TXTYPES: Record<
| 'DISTRIBUTETOKENS'
| 'PARTIALLYFREEZETOKENS'
| 'TRANSFERTOKENS'
| 'FAUCET'
| 'PAUSECONTRACT',
ITxType
> = {
Expand All @@ -48,6 +49,7 @@ export const TXTYPES: Record<
DISTRIBUTETOKENS: { name: 'DISTRIBUTETOKENS', overall: true },
PARTIALLYFREEZETOKENS: { name: 'PARTIALLYFREEZETOKENS', overall: true },
TRANSFERTOKENS: { name: 'TRANSFERTOKENS', overall: true },
FAUCET: { name: 'FAUCET', overall: false },
} as const;

export interface ITransaction {
Expand Down
189 changes: 189 additions & 0 deletions packages/apps/rwa-demo/src/hooks/__tests__/faucet.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,189 @@
import { renderHook } from '@testing-library/react-hooks';
import { useFaucet } from '../faucet';

describe('faucet hook', () => {
const mocksHook = vi.hoisted(() => {
return {
useAccount: vi.fn().mockReturnValue({
account: {
address: 'k:1',
},
sign: vi.fn(),
isMounted: true,
isAgent: true,
isOwner: true,
isInvestor: true,
isGasPayable: true,
}),
useNetwork: vi.fn().mockReturnValue({
activeNetwork: {
networkId: 'development',
},
}),
useTransactions: vi.fn().mockReturnValue({
addTransaction: vi.fn(),
isActiveAccountChangeTx: false,
}),
useNotifications: vi.fn().mockReturnValue({
addNotification: vi.fn(),
}),
};
});

beforeEach(async () => {
vi.mock('./../account', async () => {
const actual = await vi.importActual('./../account');
return {
...actual,
useAccount: mocksHook.useAccount,
};
});

vi.mock('./../networks', async () => {
const actual = await vi.importActual('./../networks');

return {
...actual,
useNetwork: mocksHook.useNetwork,
};
});

vi.mock('./../transactions', async () => {
const actual = await vi.importActual('./../transactions');
return {
...actual,
useTransactions: mocksHook.useTransactions,
};
});

vi.mock('@kadena/kode-ui/patterns', async () => {
const actual = await vi.importActual('@kadena/kode-ui/patterns');
return {
...actual,
useNotifications: mocksHook.useNotifications,
};
});
});

afterEach(() => {
vi.clearAllMocks();
});

it('should return the correct properties', async () => {
const { result } = renderHook(() => useFaucet());
expect(result.current.hasOwnProperty('isAllowed')).toBe(true);
expect(result.current.hasOwnProperty('submit')).toBe(true);
});

describe('isAllowed', () => {
it('should return true, when account is mounted, when it is owner, when gas is NOT payable, when network is development', () => {
mocksHook.useAccount.mockImplementation(() => ({
account: {
address: 'k:he-man',
},
isMounted: true,
isAgent: false,
isOwner: true,
isInvestor: false,
isGasPayable: false,
}));

mocksHook.useNetwork.mockImplementation(() => ({
...mocksHook.useNetwork.getMockImplementation(),
activeNetwork: { networkId: 'development' },
}));

const { result } = renderHook(() => useFaucet());

expect(result.current.isAllowed).toBe(true);
});
});

it('should return false, when account is NOT mounted, when it is owner, when gas is NOT payable, when network is development', () => {
mocksHook.useAccount.mockImplementation(() => ({
account: {
address: 'k:he-man',
},
isMounted: false,
isAgent: false,
isOwner: true,
isInvestor: false,
isGasPayable: false,
}));

mocksHook.useNetwork.mockImplementation(() => ({
...mocksHook.useNetwork.getMockImplementation(),
activeNetwork: { networkId: 'development' },
}));

const { result } = renderHook(() => useFaucet());

expect(result.current.isAllowed).toBe(false);
});

it('should return false, when account is mounted, when account has no role, when gas is NOT payable, when network is development', () => {
mocksHook.useAccount.mockImplementation(() => ({
account: {
address: 'k:he-man',
},
isMounted: true,
isAgent: false,
isOwner: false,
isInvestor: false,
isGasPayable: false,
}));

mocksHook.useNetwork.mockImplementation(() => ({
...mocksHook.useNetwork.getMockImplementation(),
activeNetwork: { networkId: 'development' },
}));

const { result } = renderHook(() => useFaucet());

expect(result.current.isAllowed).toBe(false);
});

it('should return false, when account is mounted, when account isinvestor, when gas is payable, when network is development', () => {
mocksHook.useAccount.mockImplementation(() => ({
account: {
address: 'k:he-man',
},
isMounted: true,
isAgent: false,
isOwner: false,
isInvestor: true,
isGasPayable: true,
}));

mocksHook.useNetwork.mockImplementation(() => ({
...mocksHook.useNetwork.getMockImplementation(),
activeNetwork: { networkId: 'development' },
}));

const { result } = renderHook(() => useFaucet());

expect(result.current.isAllowed).toBe(false);
});

it('should return false, when account is mounted, when account isagent, when gas is NOT payable, when network is mainnet', () => {
mocksHook.useAccount.mockImplementation(() => ({
account: {
address: 'k:he-man',
},
isMounted: false,
isAgent: true,
isOwner: false,
isInvestor: true,
isGasPayable: false,
}));

mocksHook.useNetwork.mockImplementation(() => ({
...mocksHook.useNetwork.getMockImplementation(),
activeNetwork: { networkId: 'mainnet01' },
}));

const { result } = renderHook(() => useFaucet());

expect(result.current.isAllowed).toBe(false);
});
});
Loading

0 comments on commit 337ea28

Please sign in to comment.