From 09dd86062ccb8e7420ead1081acf90810e41418b Mon Sep 17 00:00:00 2001 From: Fran Domovic Date: Mon, 16 Dec 2024 17:03:17 +0100 Subject: [PATCH 1/6] feat: add parsing for each proposal type --- app/src/components/proposal/Actions.tsx | 185 ++++++++++++++++++++++++ app/src/pages/home/index.tsx | 16 +- 2 files changed, 187 insertions(+), 14 deletions(-) create mode 100644 app/src/components/proposal/Actions.tsx diff --git a/app/src/components/proposal/Actions.tsx b/app/src/components/proposal/Actions.tsx new file mode 100644 index 0000000..f2723bc --- /dev/null +++ b/app/src/components/proposal/Actions.tsx @@ -0,0 +1,185 @@ +import React from 'react'; +import { styled } from 'styled-components'; + +interface FunctionCallProps { + receiver_id: string; + method_name: string; + args: string; + deposit: number; + gas: number; +} + +interface ContextValueProps { + key: number[]; + value: number[]; +} + +interface ApprovalsParams { + num_approvals: number; +} + +interface LimitParams { + active_proposals_limit: number; +} + +interface TransferParams { + receiver_id: string; + amount: number; +} + +interface Action { + scope: string; + params: + | LimitParams + | TransferParams + | ApprovalsParams + | ContextValueProps + | FunctionCallProps; +} + +interface ActionsProps { + actions: Action[]; +} + +interface TableHeaderProps { + columns: number; + bgColor?: boolean; +} + +const GridList = styled.div` + display: grid; + grid-template-columns: repeat(${(props) => props.columns}, 1fr); + gap: 0.5rem; + background: ${(props) => (props.bgColor ? '#ffa500' : 'transparent')}; +`; + +export default function Actions({ actions }: ActionsProps) { + const getColumnCount = (scope: string) => { + switch (scope) { + case 'Transfer': + case 'SetContextValue': + return 3; + case 'SetActiveProposalsLimit': + case 'SetNumApprovals': + return 2; + case 'ExternalFunctionCall': + return 5; + default: + return 1; + } + }; + + const renderActionContent = (action: Action) => { + switch (action.scope) { + case 'Transfer': + return ( + <> +
Scope
+
Amount
+
Receiver ID
+ + ); + case 'SetContextValue': + return ( + <> +
Scope
+
Key
+
Vaue ID
+ + ); + case 'SetActiveProposalsLimit': + return ( + <> +
Scope
+
Active Proposals Limit
+ + ); + case 'SetNumApprovals': + return ( + <> +
Scope
+
Number of Approvals
+ + ); + case 'ExternalFunctionCall': + return ( + <> +
Scope
+
Receiver ID
+
Method
+
Deposit
+
Gas
+ + ); + default: + return
Scope
; + } + }; + + const renderActionValues = (action: Action) => { + switch (action.scope) { + case 'Transfer': + const transferParams = action.params as TransferParams; + return ( + <> +
{action.scope}
+
{transferParams.amount}
+
{transferParams.receiver_id}
+ + ); + case 'SetContextValue': + const contextValueParams = action.params as ContextValueProps; + return ( + <> +
{action.scope}
+
{String.fromCharCode(...contextValueParams.key)}
+
{String.fromCharCode(...contextValueParams.value)}
+ + ); + case 'SetActiveProposalsLimit': + const limitParams = action.params as LimitParams; + return ( + <> +
{action.scope}
+
{limitParams.active_proposals_limit}
+ + ); + case 'SetNumApprovals': + const approvalParams = action.params as ApprovalsParams; + return ( + <> +
{action.scope}
+
{approvalParams.num_approvals}
+ + ); + case 'ExternalFunctionCall': + const functionParams = action.params as FunctionCallProps; + return ( + <> +
{action.scope}
+
{functionParams.receiver_id}
+
{functionParams.method_name}
+
{functionParams.deposit}
+
{functionParams.gas}
+ + ); + default: + return
{action.scope}
; + } + }; + + return ( + <> + + {renderActionContent(actions[0])} + +
+ {actions.map((action, index) => ( + + {renderActionValues(action)} + + ))} +
+ + ); +} diff --git a/app/src/pages/home/index.tsx b/app/src/pages/home/index.tsx index 1c4d14b..aecc645 100644 --- a/app/src/pages/home/index.tsx +++ b/app/src/pages/home/index.tsx @@ -41,6 +41,7 @@ import bs58 from 'bs58'; import CreateProposalPopup, { ProposalData, } from '../../components/proposals/CreateProposalPopup'; +import Actions from '../../components/proposal/Actions'; const FullPageCenter = styled.div` display: flex; @@ -579,20 +580,7 @@ export default function HomePage() { )}

Actions

-
-
Scope
-
Amount
-
Receiver ID
-
-
- {selectedProposal.actions.map((action, index) => ( -
-
{action.scope}
-
{action.params.amount}
-
{action.params.receiver_id}
-
- ))} -
+
approveProposal(selectedProposal.id)}> {approveProposalLoading ? 'Loading...' : 'Approve proposal'} From 384e992a08fc0f39dbaed2ac325e17eefb003ab8 Mon Sep 17 00:00:00 2001 From: alenmestrov Date: Wed, 18 Dec 2024 20:35:16 +0100 Subject: [PATCH 2/6] feat: removed protocol select field and argument, removed multiple variable adding in context variable selection --- .../proposals/CreateProposalPopup.tsx | 3 --- .../proposals/CrossContractCallForm.tsx | 18 +----------------- .../proposals/SetContextVariableForm.tsx | 10 ---------- 3 files changed, 1 insertion(+), 30 deletions(-) diff --git a/app/src/components/proposals/CreateProposalPopup.tsx b/app/src/components/proposals/CreateProposalPopup.tsx index c6a76ef..a638b5f 100644 --- a/app/src/components/proposals/CreateProposalPopup.tsx +++ b/app/src/components/proposals/CreateProposalPopup.tsx @@ -76,7 +76,6 @@ export const ButtonSm = styled.button` export interface ProposalData { actionType: string; - protocol: string; contractId: string; methodName: string; arguments: { key: string; value: string }[]; @@ -99,7 +98,6 @@ export default function CreateProposalPopup({ }: CreateProposalPopupProps) { const [proposalForm, setProposalForm] = useState({ actionType: 'Cross contract call', - protocol: 'NEAR', contractId: '', methodName: '', arguments: [{ key: '', value: '' }], @@ -195,7 +193,6 @@ export default function CreateProposalPopup({ setProposalForm({ actionType: 'Cross contract call', - protocol: 'NEAR', contractId: '', methodName: '', arguments: [{ key: '', value: '' }], diff --git a/app/src/components/proposals/CrossContractCallForm.tsx b/app/src/components/proposals/CrossContractCallForm.tsx index 0145cc0..956e723 100644 --- a/app/src/components/proposals/CrossContractCallForm.tsx +++ b/app/src/components/proposals/CrossContractCallForm.tsx @@ -30,19 +30,6 @@ export default function CrossContractCallForm({ }: CrossContractCallFormProps) { return ( <> - - - - - + - removeContextVariable(index)} - style={{ background: '#666', marginBottom: '1rem' }} - > - Remove -
))} - - Add Variable - ); } From d03aff7b5a21f4df60025b2f7249ea9e707ad0ee Mon Sep 17 00:00:00 2001 From: alenmestrov Date: Wed, 18 Dec 2024 20:35:40 +0100 Subject: [PATCH 3/6] fix: lint --- .../proposals/ChangeApprovalsNeededForm.tsx | 2 +- .../proposals/CreateProposalPopup.tsx | 4 +- .../proposals/MaxActiveProposalsForm.tsx | 2 +- .../proposals/SetContextVariableForm.tsx | 96 ++++++++++--------- app/src/components/proposals/TransferForm.tsx | 4 +- app/src/pages/home/index.tsx | 5 +- 6 files changed, 56 insertions(+), 57 deletions(-) diff --git a/app/src/components/proposals/ChangeApprovalsNeededForm.tsx b/app/src/components/proposals/ChangeApprovalsNeededForm.tsx index 74a495d..4d2c71b 100644 --- a/app/src/components/proposals/ChangeApprovalsNeededForm.tsx +++ b/app/src/components/proposals/ChangeApprovalsNeededForm.tsx @@ -19,7 +19,7 @@ export default function ChangeApprovalsNeededForm({ type="number" id="minApprovals" name="minApprovals" - placeholder='2' + placeholder="2" value={proposalForm.minApprovals} onChange={handleInputChange} min="1" diff --git a/app/src/components/proposals/CreateProposalPopup.tsx b/app/src/components/proposals/CreateProposalPopup.tsx index a638b5f..6d96125 100644 --- a/app/src/components/proposals/CreateProposalPopup.tsx +++ b/app/src/components/proposals/CreateProposalPopup.tsx @@ -29,8 +29,8 @@ const ModalContent = styled.div` color: white; h2 { - padding-bottom: 0.5rem; - margin: 0; + padding-bottom: 0.5rem; + margin: 0; } `; diff --git a/app/src/components/proposals/MaxActiveProposalsForm.tsx b/app/src/components/proposals/MaxActiveProposalsForm.tsx index 8a8d235..371cb18 100644 --- a/app/src/components/proposals/MaxActiveProposalsForm.tsx +++ b/app/src/components/proposals/MaxActiveProposalsForm.tsx @@ -19,7 +19,7 @@ export default function MaxActiveProposalsForm({ type="number" id="maxActiveProposals" name="maxActiveProposals" - placeholder='10' + placeholder="10" value={proposalForm.maxActiveProposals} onChange={handleInputChange} min="1" diff --git a/app/src/components/proposals/SetContextVariableForm.tsx b/app/src/components/proposals/SetContextVariableForm.tsx index 1ab7f85..80c275d 100644 --- a/app/src/components/proposals/SetContextVariableForm.tsx +++ b/app/src/components/proposals/SetContextVariableForm.tsx @@ -5,61 +5,63 @@ import { styled } from 'styled-components'; const ScrollWrapper = styled.div` max-height: 150px; overflow-y: auto; -` +`; interface SetContextVariableFormProps { - proposalForm: ProposalData; - handleContextVariableChange: ( - index: number, - field: 'key' | 'value', - value: string, - ) => void, - removeContextVariable: (index: number) => void, - addContextVariable: () => void, + proposalForm: ProposalData; + handleContextVariableChange: ( + index: number, + field: 'key' | 'value', + value: string, + ) => void; + removeContextVariable: (index: number) => void; + addContextVariable: () => void; } export default function SetContextVariableForm({ - proposalForm, - handleContextVariableChange, - removeContextVariable, - addContextVariable, + proposalForm, + handleContextVariableChange, + removeContextVariable, + addContextVariable, }: SetContextVariableFormProps) { return ( <> - - {proposalForm.contextVariables.map((variable: { key: string; value: string }, index: number) => ( -
- - - handleContextVariableChange(index, 'key', e.target.value) - } - required - /> - - - - handleContextVariableChange(index, 'value', e.target.value) - } - required - /> - -
- ))} + + {proposalForm.contextVariables.map( + (variable: { key: string; value: string }, index: number) => ( +
+ + + handleContextVariableChange(index, 'key', e.target.value) + } + required + /> + + + + handleContextVariableChange(index, 'value', e.target.value) + } + required + /> + +
+ ), + )}
); diff --git a/app/src/components/proposals/TransferForm.tsx b/app/src/components/proposals/TransferForm.tsx index 74a2533..9c937cb 100644 --- a/app/src/components/proposals/TransferForm.tsx +++ b/app/src/components/proposals/TransferForm.tsx @@ -20,7 +20,7 @@ export default function TransferForm({ type="text" id="receiverId" name="receiverId" - placeholder='account address' + placeholder="account address" value={proposalForm.receiverId} onChange={handleInputChange} required @@ -32,7 +32,7 @@ export default function TransferForm({ type="text" id="amount" name="amount" - placeholder='10' + placeholder="10" value={proposalForm.amount} onChange={handleInputChange} required diff --git a/app/src/pages/home/index.tsx b/app/src/pages/home/index.tsx index aecc645..693ed57 100644 --- a/app/src/pages/home/index.tsx +++ b/app/src/pages/home/index.tsx @@ -241,10 +241,7 @@ export default function HomePage() { method_name: formData.methodName, args: JSON.stringify(argsObject), deposit: formData.deposit || '0', - gas: - formData.protocol === 'NEAR' - ? '30000000000000' - : '0', + gas: formData.protocol === 'NEAR' ? '30000000000000' : '0', }, }; From 7a07a4110f0b3cb9b0e51d209c8acb1587de7576 Mon Sep 17 00:00:00 2001 From: alenmestrov Date: Wed, 18 Dec 2024 20:51:27 +0100 Subject: [PATCH 4/6] feat: added fetching of context variables --- app/src/api/contractApi.ts | 5 ++ .../api/dataSource/ContractApiDataSource.ts | 60 ++++++++++++----- app/src/pages/home/index.tsx | 67 ++++++++++++++++++- 3 files changed, 113 insertions(+), 19 deletions(-) diff --git a/app/src/api/contractApi.ts b/app/src/api/contractApi.ts index 8b3b4b5..ea2d2e5 100644 --- a/app/src/api/contractApi.ts +++ b/app/src/api/contractApi.ts @@ -39,6 +39,11 @@ export interface ApprovalsCount { num_approvals: number; } +export interface ContextVariables { + key: string; + value: string; +} + export interface ContractApi { //Contract getContractProposals( diff --git a/app/src/api/dataSource/ContractApiDataSource.ts b/app/src/api/dataSource/ContractApiDataSource.ts index e1df33c..c15a131 100644 --- a/app/src/api/dataSource/ContractApiDataSource.ts +++ b/app/src/api/dataSource/ContractApiDataSource.ts @@ -3,6 +3,7 @@ import { ApiResponse } from '@calimero-is-near/calimero-p2p-sdk'; import { ApprovalsCount, ContextDetails, + ContextVariables, ContractApi, ContractProposal, Members, @@ -103,23 +104,50 @@ export class ContextApiDataSource implements ContractApi { } } - getContextDetails(): ApiResponse { - // try { - // const headers: Header | null = await createAuthHeader( - // contextId, - // getNearEnvironment(), - // ); - // const response = await this.client.get( - // `${getAppEndpointKey()}/admin-api/contexts/${contextId}`, - // headers ?? {}, - // ); - // return response; - // } catch (error) { - // console.error('Error fetching context:', error); - // return { error: { code: 500, message: 'Failed to fetch context data.' } }; - // } - throw new Error('Method not implemented.'); + async getContextVariables(): ApiResponse { + try { + const { jwtObject, error } = getConfigAndJwt(); + if (error) { + return { error }; + } + + const apiEndpoint = `${getStorageAppEndpointKey()}/admin-api/contexts/${jwtObject.context_id}/proposals/context-storage-entries`; + const body = { + offset: 0, + limit: 10, + }; + + const response = await axios.post(apiEndpoint, body, { + headers: { + 'Content-Type': 'application/json', + }, + }); + + if (!response.data.data) { + return { + data: [], + error: null, + }; + } + + // Convert both key and value from Vec to string + const parsedData = response.data.data.map((item: any) => ({ + key: new TextDecoder().decode(new Uint8Array(item.key)), + value: new TextDecoder().decode(new Uint8Array(item.value)) + })); + + return { + data: parsedData ?? [], + error: null, + }; + } catch (error) { + return { + data: null, + error: error as Error, + }; + } } + getContextMembers(): ApiResponse { throw new Error('Method not implemented.'); } diff --git a/app/src/pages/home/index.tsx b/app/src/pages/home/index.tsx index 693ed57..c156514 100644 --- a/app/src/pages/home/index.tsx +++ b/app/src/pages/home/index.tsx @@ -35,7 +35,7 @@ import { } from '../../utils/storage'; import { useNavigate } from 'react-router-dom'; import { ContextApiDataSource } from '../../api/dataSource/ContractApiDataSource'; -import { ApprovalsCount, ContractProposal } from '../../api/contractApi'; +import { ApprovalsCount, ContextVariables, ContractProposal } from '../../api/contractApi'; import { Buffer } from 'buffer'; import bs58 from 'bs58'; import CreateProposalPopup, { @@ -153,6 +153,25 @@ const ProposalsWrapper = styled.div` } `; +const StyledTable = styled.table` + th, td { + text-align: center; + padding: 8px; + } +`; + +const ContextVariablesContainer = styled.div` + display: flex; + flex-direction: column; + align-items: center; + + .context-variables { + padding-left: 1rem; + padding-right: 1rem; + text-align: center; + } +`; + export default function HomePage() { const navigate = useNavigate(); const url = getAppEndpointKey(); @@ -170,7 +189,7 @@ export default function HomePage() { const [hasAlerted, setHasAlerted] = useState(false); const lastExecutedProposalRef = useRef(null); const [isModalOpen, setIsModalOpen] = useState(false); - + const [contextVariables, setContextVariables] = useState([]); useEffect(() => { if (!url || !applicationId || !accessToken || !refreshToken) { navigate('/auth'); @@ -398,6 +417,17 @@ export default function HomePage() { } } + async function getContextVariables() { + const result: ResponseData = + await new ContextApiDataSource().getContextVariables(); + if (result?.error) { + console.error('Error:', result.error); + } else { + // @ts-ignore + setContextVariables(result.data); + } + } + useEffect(() => { const setProposalData = async () => { await getProposalApprovals(); @@ -511,7 +541,38 @@ export default function HomePage() { Blockchain proposals demo application - + +
+ getContextVariables()}> + Get Context Variables + +
+
+

Context variables:

+ {contextVariables.length > 0 ? ( +
+ + + + Key + Value + + + + {contextVariables.map((variable) => ( + + {variable.key} + {variable.value} + + ))} + + +
+ ) : ( +
No context variables
+ )} +
+
Proposals