diff --git a/app/package.json b/app/package.json index 8b854e1..a3f7b01 100644 --- a/app/package.json +++ b/app/package.json @@ -3,9 +3,9 @@ "version": "0.0.0", "private": true, "type": "module", - "homepage": "https://calimero-network.github.io/core-app-template/", + "homepage": "https://calimero-network.github.io/demo-blockchain-integrations/", "dependencies": { - "@calimero-is-near/calimero-p2p-sdk": "0.0.41", + "@calimero-is-near/calimero-p2p-sdk": "0.1.2", "@types/react": "^18.3.3", "@types/react-dom": "^18.3.0", "axios": "^1.6.7", @@ -28,6 +28,7 @@ "@typescript-eslint/eslint-plugin": "^7.13.0", "@typescript-eslint/parser": "^7.13.0", "@vitejs/plugin-react": "^4.2.1", + "bs58": "^6.0.0", "concurrently": "^7.3.0", "eslint": "^8.57.0", "eslint-config-prettier": "^9.1.0", diff --git a/app/pnpm-lock.yaml b/app/pnpm-lock.yaml index 69db8ca..03d8483 100644 --- a/app/pnpm-lock.yaml +++ b/app/pnpm-lock.yaml @@ -8,8 +8,8 @@ importers: .: dependencies: '@calimero-is-near/calimero-p2p-sdk': - specifier: 0.0.41 - version: 0.0.41(@near-wallet-selector/modal-ui@8.9.8(near-api-js@3.0.4)) + specifier: 0.1.2 + version: 0.1.2(@near-wallet-selector/modal-ui@8.9.8(near-api-js@3.0.4)) '@types/react': specifier: ^18.3.3 version: 18.3.3 @@ -65,6 +65,9 @@ importers: '@vitejs/plugin-react': specifier: ^4.2.1 version: 4.3.0(vite@5.2.12(@types/node@16.18.98)(terser@5.31.0)) + bs58: + specifier: ^6.0.0 + version: 6.0.0 concurrently: specifier: ^7.3.0 version: 7.6.0 @@ -1370,10 +1373,10 @@ packages: } engines: { node: '>=6.9.0' } - '@calimero-is-near/calimero-p2p-sdk@0.0.41': + '@calimero-is-near/calimero-p2p-sdk@0.1.2': resolution: { - integrity: sha512-GWRTqNhzPSGnoXgAciKOL5ptNIpO6JREzyEOD6cybucbUkrwViopaSBf/0JbHKIXHgA/3lDdrZB1i0bO9ykaow==, + integrity: sha512-6U0XWOoozMrbEvrR7wcBWSe7JsNzTOCV7SCxTtVDs9iE6MPymdqdEiKpFVuLY356uE3u7YGKQlEe2Tiq/ylRLw==, } peerDependencies: '@near-wallet-selector/modal-ui': ^8.9.7 @@ -2597,6 +2600,12 @@ packages: integrity: sha512-2A8QhOMrbomlDuiLeK9XibIBzuHeRcqqNOHp0Cyp5EoJ1IFDh+XZH3A6BkXtv0K4gFGCI0Y4BM7B1wOEi0Rmgw==, } + axios@1.7.7: + resolution: + { + integrity: sha512-S4kL7XrjgBmvdGut0sN3yJxqYzrDOnivkBiN0OFs6hLiUam3UPvswUo0kqGyhqUZGEOytHyumEdXsAkgCOUf3Q==, + } + axobject-query@3.2.1: resolution: { @@ -2666,6 +2675,12 @@ packages: integrity: sha512-H7JU6iBHTal1gp56aKoaa//YUxEaAOUiydvrV/pILqIHXTtqxSkATOnDA2u+jZ/61sD+L/412+7kzXRtWukhpQ==, } + base-x@5.0.0: + resolution: + { + integrity: sha512-sMW3VGSX1QWVFA6l8U62MLKz29rRfpTlYdCqLdpLo1/Yd4zZwSbnUaDfciIAowAqvq7YFnWq9hrhdg1KYgc1lQ==, + } + base64-js@1.5.1: resolution: { @@ -2784,6 +2799,12 @@ packages: integrity: sha512-Ok3Wdf5vOIlBrgCvTq96gBkJw+JUEzdBgyaza5HLtPm7yTHkjRy8+JzNyHF7BHa0bNWOQIp3m5YF0nnFcOIKLw==, } + bs58@6.0.0: + resolution: + { + integrity: sha512-PD0wEnEYg6ijszw/u8s+iI3H17cTymlrwkKhDhPZq+Sokl3AU4htyBFTjAeNAlCCmg0f53g6ih3jATyCKftTfw==, + } + buffer-from@1.1.2: resolution: { @@ -7015,10 +7036,10 @@ snapshots: '@babel/helper-validator-identifier': 7.24.7 to-fast-properties: 2.0.0 - '@calimero-is-near/calimero-p2p-sdk@0.0.41(@near-wallet-selector/modal-ui@8.9.8(near-api-js@3.0.4))': + '@calimero-is-near/calimero-p2p-sdk@0.1.2(@near-wallet-selector/modal-ui@8.9.8(near-api-js@3.0.4))': dependencies: '@near-wallet-selector/modal-ui': 8.9.8(near-api-js@3.0.4) - axios: 1.7.2 + axios: 1.7.7 jwt-decode: 4.0.0 near-api-js: 3.0.4 react: 18.3.1 @@ -7818,6 +7839,14 @@ snapshots: transitivePeerDependencies: - debug + axios@1.7.7: + dependencies: + follow-redirects: 1.15.6 + form-data: 4.0.0 + proxy-from-env: 1.1.0 + transitivePeerDependencies: + - debug + axobject-query@3.2.1: dependencies: dequal: 2.0.3 @@ -7886,6 +7915,8 @@ snapshots: dependencies: safe-buffer: 5.2.1 + base-x@5.0.0: {} + base64-js@1.5.1: {} bn.js@4.12.0: {} @@ -7978,6 +8009,10 @@ snapshots: dependencies: base-x: 3.0.9 + bs58@6.0.0: + dependencies: + base-x: 5.0.0 + buffer-from@1.1.2: optional: true diff --git a/app/src/App.tsx b/app/src/App.tsx index 4e6bfa7..5c114c7 100644 --- a/app/src/App.tsx +++ b/app/src/App.tsx @@ -9,7 +9,7 @@ import { getNodeUrl } from './utils/node'; export default function App() { return ( - + } /> } /> diff --git a/app/src/api/clientApi.ts b/app/src/api/clientApi.ts index 300a7eb..68042e9 100644 --- a/app/src/api/clientApi.ts +++ b/app/src/api/clientApi.ts @@ -1,27 +1,67 @@ import { ApiResponse } from '@calimero-is-near/calimero-p2p-sdk'; -export interface GetCountResponse { - count: number; +export interface Message { + id: String; + proposal_id: String; + author: String; + text: String; + created_at: String; } -export interface IncreaseCountRequest { - count: number; +export interface GetProposalMessagesRequest { + // proposalId: String; + proposal_id: String; } -export interface IncreaseCountResponse {} +export interface GetProposalMessagesResponse { + messages: Message[]; +} + +export interface SendProposalMessageRequest { + // proposalId: String; + proposal_id: String; + message: Message; +} + +export interface SendProposalMessageResponse { + result: boolean; +} + +export interface CreateProposalRequest { + receiver: String; +} + +export interface CreateProposalResponse { + proposal_id: String; +} -export interface ResetCounterResponse {} +export interface ApproveProposalRequest { + proposal_id: string; +} + +export interface ApproveProposalResponse { + success: boolean; +} export enum ClientMethod { - GET_COUNT = 'get_count', - INCREASE_COUNT = 'increase_count', - RESET = 'reset', + GET_PROPOSAL_MESSAGES = 'get_proposal_messages', + SEND_PROPOSAL_MESSAGE = 'send_proposal_messages', + CREATE_PROPOSAL_MESSAGES = 'create_new_proposal', + APPROVE_PROPOSAL_MESSAGE = 'approve_proposal', } export interface ClientApi { - getCount(): ApiResponse; - increaseCount( - params: IncreaseCountRequest, - ): ApiResponse; - reset(): ApiResponse; + //Cali Storage + getProposalMessages( + proposalsRequest: GetProposalMessagesRequest, + ): ApiResponse; + sendProposalMessage( + sendMessageRequest: SendProposalMessageRequest, + ): ApiResponse; + createProposal( + request: CreateProposalRequest, + ): ApiResponse; + approveProposal( + request: ApproveProposalRequest, + ): ApiResponse; } diff --git a/app/src/api/contractApi.ts b/app/src/api/contractApi.ts new file mode 100644 index 0000000..a86944c --- /dev/null +++ b/app/src/api/contractApi.ts @@ -0,0 +1,78 @@ +import { ApiResponse } from '@calimero-is-near/calimero-p2p-sdk'; +import { GetProposalsRequest } from './dataSource/ContractApiDataSource'; + +export interface ContextDetails {} + +export interface Members { + publicKey: String; +} + +export interface ProposalAction { + scope: string; + params: { + amount: number; + receiver_id: string; + }; +} + +export interface ContractProposal { + id: string; + author_id: string; + actions: ProposalAction[]; +} + +// +export interface CalimeroProposalMetadata {} + +export interface ContextDetails {} + +export interface Members { + publicKey: String; +} + +export interface Message { + publicKey: String; +} + +export interface ApprovalsCount { + proposal_id: string; + num_approvals: number; +} + +export interface ContractApi { + //Contract + getContractProposals( + request: GetProposalsRequest, + ): ApiResponse; + getNumOfProposals(): ApiResponse; + getProposalApprovals(proposalId: String): ApiResponse; + getContextDetails(contextId: String): ApiResponse; + getContextMembers(): ApiResponse; + getContextMembersCount(): ApiResponse; +} + +// async removeProposal(proposalId: String): ApiResponse { +// return await this.client.delete( +// `${this.endpoint}/admin-api/contexts/${this.contextId}/proposals/${proposalId}`, +// ); +// } + +// async getProposalMessage( +// proposalId: String, +// messageId: String, +// ): ApiResponse { +// return await this.client.get( +// `${this.endpoint}/admin-api/contexts/${this.contextId}/proposals/${proposalId}/messages/${messageId}`, +// ); +// } + +// async getProposalMessages(proposalId: String): ApiResponse { +// return await this.client.get( +// `${this.endpoint}/admin-api/contexts/${this.contextId}/proposals/${proposalId}/messages`, +// ); +// } +// async approveProposal(proposalId: String): ApiResponse { +// return await this.client.post( +// `${this.endpoint}/admin-api/contexts/${this.contextId}/proposals/${proposalId}/vote`, +// ); +// } diff --git a/app/src/api/dataSource/ClientApiDataSource.ts b/app/src/api/dataSource/ClientApiDataSource.ts deleted file mode 100644 index b4a12ca..0000000 --- a/app/src/api/dataSource/ClientApiDataSource.ts +++ /dev/null @@ -1,156 +0,0 @@ -import { - ApiResponse, - JsonRpcClient, - RequestConfig, - WsSubscriptionsClient, - RpcError, - handleRpcError, -} from '@calimero-is-near/calimero-p2p-sdk'; -import { - ClientApi, - ClientMethod, - GetCountResponse, - IncreaseCountRequest, - IncreaseCountResponse, - ResetCounterResponse, -} from '../clientApi'; -import { getContextId, getNodeUrl } from '../../utils/node'; -import { - getJWTObject, - getStorageAppEndpointKey, - JsonWebToken, -} from '../../utils/storage'; -import { AxiosHeader, createJwtHeader } from '../../utils/jwtHeaders'; -import { getRpcPath } from '../../utils/env'; - -export function getJsonRpcClient() { - return new JsonRpcClient(getStorageAppEndpointKey() ?? '', getRpcPath()); -} - -export function getWsSubscriptionsClient() { - return new WsSubscriptionsClient(getStorageAppEndpointKey() ?? '', '/ws'); -} - -function getConfigAndJwt() { - const jwtObject: JsonWebToken | null = getJWTObject(); - const headers: AxiosHeader | null = createJwtHeader(); - if (!headers) { - return { - error: { message: 'Failed to create auth headers', code: 500 }, - }; - } - if (!jwtObject) { - return { - error: { message: 'Failed to get JWT token', code: 500 }, - }; - } - if (jwtObject.executor_public_key === null) { - return { - error: { message: 'Failed to get executor public key', code: 500 }, - }; - } - - const config: RequestConfig = { - headers: headers, - timeout: 10000, - }; - - return { jwtObject, config }; -} - -export class ClientApiDataSource implements ClientApi { - private async handleError( - error: RpcError, - params: any, - callbackFunction: any, - ) { - if (error && error.code) { - const response = await handleRpcError(error, getNodeUrl); - if (response.code === 403) { - return await callbackFunction(params); - } - return { - error: await handleRpcError(error, getNodeUrl), - }; - } - } - async getCount(): ApiResponse { - const { jwtObject, config, error } = getConfigAndJwt(); - if (error) { - return { error }; - } - - const response = await getJsonRpcClient().query( - { - contextId: jwtObject?.context_id ?? getContextId(), - method: ClientMethod.GET_COUNT, - argsJson: {}, - executorPublicKey: jwtObject.executor_public_key, - }, - config, - ); - if (response?.error) { - return await this.handleError(response.error, {}, this.getCount); - } - - return { - data: { count: Number(response?.result?.output) ?? 0 }, - error: null, - }; - } - - async increaseCount( - params: IncreaseCountRequest, - ): ApiResponse { - const { jwtObject, config, error } = getConfigAndJwt(); - if (error) { - return { error }; - } - - const response = await getJsonRpcClient().mutate< - IncreaseCountRequest, - IncreaseCountResponse - >( - { - contextId: jwtObject?.context_id ?? getContextId(), - method: ClientMethod.INCREASE_COUNT, - argsJson: params, - executorPublicKey: jwtObject.executor_public_key, - }, - config, - ); - if (response?.error) { - return await this.handleError(response.error, {}, this.increaseCount); - } - - return { - data: Number(response?.result?.output) ?? null, - error: null, - }; - } - - async reset(): ApiResponse { - const { jwtObject, config, error } = getConfigAndJwt(); - if (error) { - return { error }; - } - - const response = await getJsonRpcClient().mutate( - { - contextId: jwtObject?.context_id ?? getContextId(), - method: ClientMethod.RESET, - argsJson: {}, - executorPublicKey: jwtObject.executor_public_key, - }, - config, - ); - if (response?.error) { - return await this.handleError(response.error, {}, this.reset); - } - - return { - data: Number(response?.result?.output) ?? null, - error: null, - }; - } -} diff --git a/app/src/api/dataSource/ContractApiDataSource.ts b/app/src/api/dataSource/ContractApiDataSource.ts new file mode 100644 index 0000000..03ad049 --- /dev/null +++ b/app/src/api/dataSource/ContractApiDataSource.ts @@ -0,0 +1,130 @@ +import { ApiResponse } from '@calimero-is-near/calimero-p2p-sdk'; + +import { + ApprovalsCount, + ContextDetails, + ContractApi, + ContractProposal, + Members, +} from '../contractApi'; +import { getStorageAppEndpointKey } from '../../utils/storage'; +import axios from 'axios'; +import { getConfigAndJwt } from './LogicApiDataSource'; + +export interface GetProposalsRequest { + offset: number; + limit: number; +} + +export class ContextApiDataSource implements ContractApi { + async getContractProposals( + request: GetProposalsRequest, + ): ApiResponse { + try { + const { jwtObject, error } = getConfigAndJwt(); + if (error) { + return { error }; + } + + const apiEndpoint = `${getStorageAppEndpointKey()}/admin-api/contexts/${jwtObject.context_id}/proposals`; + const body = request; + + const response = await axios.post(apiEndpoint, body, { + headers: { + 'Content-Type': 'application/json', + }, + }); + + return { + data: response.data ?? [], + error: null, + }; + } catch (error) { + return { + data: null, + error: error as Error, + }; + } + } + + async getProposalApprovals(proposalId: String): ApiResponse { + try { + const { jwtObject, error } = getConfigAndJwt(); + if (error) { + return { error }; + } + + const apiEndpoint = `${getStorageAppEndpointKey()}/admin-api/contexts/${jwtObject.context_id}/proposals/${proposalId}/approvals/count`; + + const response = await axios.get(apiEndpoint); + + return { + data: response.data ?? [], + error: null, + }; + } catch (error) { + return { + data: null, + error: error as Error, + }; + } + } + + async getNumOfProposals(): ApiResponse { + try { + const { jwtObject, error } = getConfigAndJwt(); + if (error) { + return { error }; + } + + const apiEndpointLimit = `${getStorageAppEndpointKey()}/admin-api/contexts/${jwtObject.context_id}/proposals/count`; + const limitResponse = await axios.get(apiEndpointLimit); + + const apiEndpoint = `${getStorageAppEndpointKey()}/admin-api/contexts/${jwtObject.context_id}/proposals`; + const body = { + offset: 0, + limit: limitResponse.data.data, + }; + + const response = await axios.post(apiEndpoint, body, { + headers: { + 'Content-Type': 'application/json', + }, + }); + + return { + data: response.data.data.length ?? 0, + error: null, + }; + } catch (error) { + return { + data: null, + error: error as Error, + }; + } + } + + 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.'); + } + getContextMembers(): ApiResponse { + throw new Error('Method not implemented.'); + } + getContextMembersCount(): ApiResponse { + throw new Error('Method not implemented.'); + } +} diff --git a/app/src/api/dataSource/LogicApiDataSource.ts b/app/src/api/dataSource/LogicApiDataSource.ts new file mode 100644 index 0000000..4f20459 --- /dev/null +++ b/app/src/api/dataSource/LogicApiDataSource.ts @@ -0,0 +1,229 @@ +import { + ApiResponse, + JsonRpcClient, + RequestConfig, + WsSubscriptionsClient, + RpcError, + handleRpcError, + RpcQueryParams, +} from '@calimero-is-near/calimero-p2p-sdk'; +import { + ApproveProposalRequest, + ApproveProposalResponse, + ClientApi, + ClientMethod, + CreateProposalRequest, + CreateProposalResponse, + GetProposalMessagesRequest, + GetProposalMessagesResponse, + SendProposalMessageRequest, + SendProposalMessageResponse, +} from '../clientApi'; +import { getContextId, getNodeUrl } from '../../utils/node'; +import { + getJWTObject, + getStorageAppEndpointKey, + JsonWebToken, +} from '../../utils/storage'; +import { AxiosHeader, createJwtHeader } from '../../utils/jwtHeaders'; +import { getRpcPath } from '../../utils/env'; + +export function getJsonRpcClient() { + return new JsonRpcClient(getStorageAppEndpointKey() ?? '', getRpcPath()); +} + +export function getWsSubscriptionsClient() { + return new WsSubscriptionsClient(getStorageAppEndpointKey() ?? '', '/ws'); +} + +export function getConfigAndJwt() { + const jwtObject: JsonWebToken | null = getJWTObject(); + const headers: AxiosHeader | null = createJwtHeader(); + if (!headers) { + return { + error: { message: 'Failed to create auth headers', code: 500 }, + }; + } + if (!jwtObject) { + return { + error: { message: 'Failed to get JWT token', code: 500 }, + }; + } + if (jwtObject.executor_public_key === null) { + return { + error: { message: 'Failed to get executor public key', code: 500 }, + }; + } + + const config: RequestConfig = { + headers: headers, + timeout: 10000, + }; + + return { jwtObject, config }; +} + +export class LogicApiDataSource implements ClientApi { + async createProposal( + request: CreateProposalRequest, + ): ApiResponse { + const { jwtObject, config, error } = getConfigAndJwt(); + if (error) { + return { error }; + } + + const params: RpcQueryParams = { + contextId: jwtObject?.context_id ?? getContextId(), + method: ClientMethod.CREATE_PROPOSAL_MESSAGES, + argsJson: request, + executorPublicKey: jwtObject.executor_public_key, + }; + + const response = await getJsonRpcClient().execute< + CreateProposalRequest, + CreateProposalResponse + >(params, config); + + if (response?.error) { + return await this.handleError(response.error, {}, this.createProposal); + } + + return { + data: response.result.output as CreateProposalResponse, + error: null, + }; + } + + async approveProposal( + request: ApproveProposalRequest, + ): ApiResponse { + const { jwtObject, config, error } = getConfigAndJwt(); + if (error) { + return { error }; + } + + console.log('appoveProposal', request); + + const params: RpcQueryParams = { + contextId: jwtObject?.context_id ?? getContextId(), + method: ClientMethod.APPROVE_PROPOSAL_MESSAGE, + argsJson: request, + executorPublicKey: jwtObject.executor_public_key, + }; + + const response = await getJsonRpcClient().execute< + ApproveProposalRequest, + ApproveProposalResponse + >(params, config); + + console.log('appoveProposal response', response); + + if (response?.error) { + return await this.handleError(response.error, {}, this.approveProposal); + } + + let result: ApproveProposalResponse = { + success: response?.result?.output?.success ?? false, + }; + + return { + data: result, + error: null, + }; + } + + async getProposalMessages( + request: GetProposalMessagesRequest, + ): ApiResponse { + const { jwtObject, config, error } = getConfigAndJwt(); + if (error) { + return { error }; + } + + console.log('getProposalMessages', request); + + const params: RpcQueryParams = { + contextId: jwtObject?.context_id ?? getContextId(), + method: ClientMethod.GET_PROPOSAL_MESSAGES, + argsJson: request, + executorPublicKey: jwtObject.executor_public_key, + }; + + const response = await getJsonRpcClient().query< + GetProposalMessagesRequest, + GetProposalMessagesResponse + >(params, config); + + console.log('getProposalMessages response', response); + + if (response?.error) { + return await this.handleError( + response.error, + {}, + this.getProposalMessages, + ); + } + + let getProposalsResponse: GetProposalMessagesResponse = { + messages: response?.result?.output?.messages, + } as GetProposalMessagesResponse; + + return { + data: getProposalsResponse, + error: null, + }; + } + async sendProposalMessage( + request: SendProposalMessageRequest, + ): ApiResponse { + const { jwtObject, config, error } = getConfigAndJwt(); + if (error) { + return { error }; + } + + const response = await getJsonRpcClient().execute< + SendProposalMessageRequest, + SendProposalMessageResponse + >( + { + contextId: jwtObject?.context_id ?? getContextId(), + method: ClientMethod.SEND_PROPOSAL_MESSAGE, + argsJson: request, + executorPublicKey: jwtObject.executor_public_key, + }, + config, + ); + if (response?.error) { + return await this.handleError( + response.error, + {}, + this.sendProposalMessage, + ); + } + + let sendMessageResponse: SendProposalMessageResponse = { + result: response?.result?.output?.result, + } as SendProposalMessageResponse; + + return { + data: sendMessageResponse, + error: null, + }; + } + + private async handleError( + error: RpcError, + params: any, + callbackFunction: any, + ) { + if (error && error.code) { + const response = await handleRpcError(error, getNodeUrl); + if (response.code === 403) { + return await callbackFunction(params); + } + return { + error: await handleRpcError(error, getNodeUrl), + }; + } + } +} diff --git a/app/src/pages/home/index.tsx b/app/src/pages/home/index.tsx index 9c75704..bd783a2 100644 --- a/app/src/pages/home/index.tsx +++ b/app/src/pages/home/index.tsx @@ -4,25 +4,29 @@ import { getAccessToken, getAppEndpointKey, getRefreshToken, - NodeEvent, ResponseData, - SubscriptionsClient, } from '@calimero-is-near/calimero-p2p-sdk'; import React, { useEffect, useState } from 'react'; import styled from 'styled-components'; +import { LogicApiDataSource } from '../../api/dataSource/LogicApiDataSource'; import { - ClientApiDataSource, - getWsSubscriptionsClient, -} from '../../api/dataSource/ClientApiDataSource'; -import { - GetCountResponse, - IncreaseCountRequest, - IncreaseCountResponse, - ResetCounterResponse, + ApproveProposalRequest, + ApproveProposalResponse, + CreateProposalRequest, + CreateProposalResponse, + GetProposalMessagesRequest, + GetProposalMessagesResponse, + SendProposalMessageRequest, } from '../../api/clientApi'; -import { getContextId, getStorageApplicationId } from '../../utils/node'; -import { clearApplicationId } from '../../utils/storage'; +import { getStorageApplicationId } from '../../utils/node'; +import { + clearApplicationId, + getJWTObject, + getStorageExecutorPublicKey, +} from '../../utils/storage'; import { useNavigate } from 'react-router-dom'; +import { ContextApiDataSource } from '../../api/dataSource/ContractApiDataSource'; +import { ApprovalsCount, ContractProposal } from '../../api/contractApi'; const FullPageCenter = styled.div` display: flex; @@ -40,40 +44,44 @@ const TextStyle = styled.div` font-size: 2em; `; -const Button = styled.div` +const Button = styled.button` color: white; padding: 0.25em 1em; + margin: 0.25em; border-radius: 8px; font-size: 2em; background: #5dbb63; cursor: pointer; justify-content: center; display: flex; + border: none; + outline: none; `; -const ButtonReset = styled.div` +const ButtonSm = styled.button` color: white; padding: 0.25em 1em; + margin: 0.25em; border-radius: 8px; - font-size: 1em; - background: #ffa500; + font-size: 1rem; + background: #5dbb63; cursor: pointer; justify-content: center; display: flex; - margin-top: 1rem; + border: none; + outline: none; `; -const StatusTitle = styled.div` - color: white; - justify-content: center; - display: flex; -`; - -const StatusValue = styled.div` +const ButtonReset = styled.div` color: white; - font-size: 60px; + padding: 0.25em 1em; + border-radius: 8px; + font-size: 1em; + background: #ffa500; + cursor: pointer; justify-content: center; display: flex; + margin-top: 1rem; `; const LogoutButton = styled.div` @@ -88,13 +96,74 @@ const LogoutButton = styled.div` display: flex; `; +const ProposalsWrapper = styled.div` + .select-dropdown { + text-align: center; + color: white; + padding: 0.25em 1em; + margin: 0.25em; + border-radius: 8px; + font-size: 1rem; + background: #5dbb63; + cursor: pointer; + justify-content: center; + display: flex; + border: none; + outline: none; + } + + .proposal-data { + font-size: 0.75rem; + padding-left: 1rem; + padding-right: 1rem; + } + + .actions-headers { + display: flex; + justify-content: space-between; + padding-left: 0.5rem; + padding-right: 0.5rem; + } + + .flex-container { + display: flex; + gap: 0.5rem; + align-items: center; + } + + .center { + justify-content: center; + margin-top: 0.5rem; + } + + .title { + padding: 0; + margin: 0; + } + + .actions-title { + padding-top: 0.5rem; + } + + .highlight { + background: #ffa500; + } +`; + export default function HomePage() { const navigate = useNavigate(); const url = getAppEndpointKey(); const applicationId = getStorageApplicationId(); const accessToken = getAccessToken(); const refreshToken = getRefreshToken(); - const [count, setCount] = useState(null); + const [createProposalLoading, setCreateProposalLoading] = useState(false); + const [proposals, setProposals] = useState([]); + const [selectedProposal, setSelectedProposal] = useState(); + const [selectedProposalApprovals, setSelectedProposalApprovals] = useState< + null | number + >(null); + const [proposalCount, setProposalCount] = useState(0); + const [approveProposalLoading, setApproveProposalLoading] = useState(false); useEffect(() => { if (!url || !applicationId || !accessToken || !refreshToken) { @@ -102,67 +171,157 @@ export default function HomePage() { } }, [accessToken, applicationId, navigate, refreshToken, url]); - async function increaseCounter() { - const params: IncreaseCountRequest = { - count: 1, + async function fetchProposalMessages(proposalId: String) { + const params: GetProposalMessagesRequest = { + proposal_id: proposalId, }; - const result: ResponseData = - await new ClientApiDataSource().increaseCount(params); + const result: ResponseData = + await new LogicApiDataSource().getProposalMessages(params); if (result?.error) { console.error('Error:', result.error); window.alert(`${result.error.message}`); return; } - await getCount(); } - async function getCount() { - const result: ResponseData = - await new ClientApiDataSource().getCount(); + async function sendProposalMessage(request: SendProposalMessageRequest) { + const params: SendProposalMessageRequest = { + proposal_id: request.proposal_id, + message: { + id: request.message.id, + proposal_id: request.proposal_id, + author: request.message.author, + text: request.message.text, + created_at: new Date().toISOString(), + }, + }; + const result: ResponseData = + await new LogicApiDataSource().sendProposalMessage(params); if (result?.error) { console.error('Error:', result.error); window.alert(`${result.error.message}`); return; } - if (result.data.count || result.data.count === 0) { - setCount(Number(result.data.count)); - } } - async function resetCount() { - const result: ResponseData = - await new ClientApiDataSource().reset(); + async function createProposal() { + setCreateProposalLoading(true); + let request: CreateProposalRequest = { + receiver: 'vuki.testnet', + }; + + const result: ResponseData = + await new LogicApiDataSource().createProposal(request); if (result?.error) { console.error('Error:', result.error); window.alert(`${result.error.message}`); + setCreateProposalLoading(false); return; } - await getCount(); + window.alert(`Proposal with id: ${result.data} created successfully`); + setCreateProposalLoading(false); } - useEffect(() => { - getCount(); - }, []); - - const observeNodeEvents = async () => { - let subscriptionsClient: SubscriptionsClient = getWsSubscriptionsClient(); - await subscriptionsClient.connect(); - subscriptionsClient.subscribe([getContextId()]); - - subscriptionsClient?.addCallback((data: NodeEvent) => { - if (data.data.events && data.data.events.length > 0) { - let currentValue = String.fromCharCode(...data.data.events[0].data); - let currentValueInt = isNaN(parseInt(currentValue)) - ? 0 - : parseInt(currentValue); - setCount(currentValueInt); + const getProposals = async () => { + const result: ResponseData = + await new ContextApiDataSource().getContractProposals({ + offset: 0, + limit: 10, + }); + if (result?.error) { + console.error('Error:', result.error); + } else { + // @ts-ignore + setProposals(result.data.data); + } + }; + + const getProposalApprovals = async () => { + if (selectedProposal) { + const result: ResponseData = + await new ContextApiDataSource().getProposalApprovals( + selectedProposal?.id, + ); + if (result?.error) { + console.error('Error:', result.error); + } else { + // @ts-ignore + setSelectedProposalApprovals(result.data.data.num_approvals); } - }); + } }; + async function getNumOfProposals() { + const result: ResponseData = + await new ContextApiDataSource().getNumOfProposals(); + if (result?.error) { + console.error('Error:', result.error); + } else { + // @ts-ignore + setProposalCount(result.data); + } + } + useEffect(() => { - observeNodeEvents(); - }, []); + const setProposalData = async () => { + await getProposalApprovals(); + await getProposals(); + await getNumOfProposals(); + }; + setProposalData(); + const intervalId = setInterval(setProposalData, 5000); + return () => clearInterval(intervalId); + }, [selectedProposal]); + + async function approveProposal(proposalId: string) { + setApproveProposalLoading(true); + let request: ApproveProposalRequest = { + proposal_id: proposalId, + }; + + const result: ResponseData = + await new LogicApiDataSource().approveProposal(request); + if (result?.error) { + console.error('Error:', result.error); + window.alert(`${result.error.message}`); + setApproveProposalLoading(false); + return; + } + setApproveProposalLoading(false); + window.alert(`Proposal approved successfully`); + } + + async function getContextDetails() { + //TODO implement this function + } + + async function getContextMembers() { + //TODO implement this function + } + + async function getContextMembersCount() { + //TODO implement this function + } + + // const observeNodeEvents = async () => { + // let subscriptionsClient: SubscriptionsClient = getWsSubscriptionsClient(); + // await subscriptionsClient.connect(); + // subscriptionsClient.subscribe([getContextId()]); + + // subscriptionsClient?.addCallback((data: NodeEvent) => { + // if (data.data.events && data.data.events.length > 0) { + // let currentValue = String.fromCharCode(...data.data.events[0].data); + // let currentValueInt = isNaN(parseInt(currentValue)) + // ? 0 + // : parseInt(currentValue); + // setCount(currentValueInt); + // } + // }); + // }; + + // useEffect(() => { + // observeNodeEvents(); + // }, []); const logout = () => { clearAppEndpoint(); @@ -177,10 +336,93 @@ export default function HomePage() { Welcome to home page! - Current count is: - {count ?? '-'} - - Reset +
Proposals
+ + + + +
+

Number of proposals:

+ {proposalCount} +
+ + {selectedProposal && ( +
+
+

Proposal ID:

+ {selectedProposal.id} +
+
+

Author ID:

+ {selectedProposal.author_id} +
+
+

Number of approvals:

+ {selectedProposalApprovals} +
+ +

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'} + + { + fetchProposalMessages(selectedProposal.id); + }} + > + Get Messages + + { + sendProposalMessage({ + proposal_id: selectedProposal.id, + message: { + id: 'test' + Math.random(), + proposal_id: selectedProposal.id, + author: getJWTObject()?.executor_public_key, + text: 'test' + Math.random(), + created_at: new Date().toISOString(), + }, + } as SendProposalMessageRequest); + }} + > + Send Message + +
+
+ )} +
Logout ); diff --git a/app/vite.config.js b/app/vite.config.js index cdcd3ea..1756923 100644 --- a/app/vite.config.js +++ b/app/vite.config.js @@ -5,7 +5,7 @@ import { resolve } from 'path'; // https://vitejs.dev/config/ export default defineConfig({ - base: '/core-app-template/', + base: '/demo-blockchain-integrations/', build: { outDir: 'build', rollupOptions: { diff --git a/logic/Cargo.lock b/logic/Cargo.lock index 2075479..6424bdc 100644 --- a/logic/Cargo.lock +++ b/logic/Cargo.lock @@ -1,6 +1,15 @@ # This file is automatically @generated by Cargo. # It is not intended for manual editing. -version = 3 +version = 4 + +[[package]] +name = "block-buffer" +version = "0.10.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71" +dependencies = [ + "generic-array", +] [[package]] name = "borsh" @@ -26,10 +35,15 @@ dependencies = [ "syn_derive", ] +[[package]] +name = "byteorder" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" + [[package]] name = "calimero-sdk" version = "0.1.0" -source = "git+https://github.com/calimero-network/core?branch=master#1296f7d45974d63c3784d4d64fb29a38b5328652" dependencies = [ "borsh", "calimero-sdk-macros", @@ -41,7 +55,6 @@ dependencies = [ [[package]] name = "calimero-sdk-macros" version = "0.1.0" -source = "git+https://github.com/calimero-network/core?branch=master#1296f7d45974d63c3784d4d64fb29a38b5328652" dependencies = [ "prettyplease", "proc-macro2", @@ -50,6 +63,32 @@ dependencies = [ "thiserror", ] +[[package]] +name = "calimero-storage" +version = "0.1.0" +dependencies = [ + "borsh", + "calimero-sdk", + "calimero-storage-macros", + "eyre", + "fixedstr", + "hex", + "indexmap", + "rand", + "serde", + "sha2", + "thiserror", +] + +[[package]] +name = "calimero-storage-macros" +version = "0.1.0" +dependencies = [ + "borsh", + "quote", + "syn", +] + [[package]] name = "cfg-if" version = "1.0.0" @@ -62,30 +101,112 @@ version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724" +[[package]] +name = "cpufeatures" +version = "0.2.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "608697df725056feaccfa42cffdaeeec3fccc4ffc38358ecd19b243e716a78e0" +dependencies = [ + "libc", +] + +[[package]] +name = "crypto-common" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" +dependencies = [ + "generic-array", + "typenum", +] + +[[package]] +name = "digest" +version = "0.10.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" +dependencies = [ + "block-buffer", + "crypto-common", +] + [[package]] name = "equivalent" version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" +[[package]] +name = "eyre" +version = "0.6.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7cd915d99f24784cdc19fd37ef22b97e3ff0ae756c7e492e9fbfe897d61e2aec" +dependencies = [ + "indenter", + "once_cell", +] + +[[package]] +name = "fixedstr" +version = "0.5.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "60aba7afd9b1b9e1950c2b7e8bcac3cc44a273c62a02717dedca2d0a1aee694d" +dependencies = [ + "serde", +] + +[[package]] +name = "generic-array" +version = "0.14.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" +dependencies = [ + "typenum", + "version_check", +] + +[[package]] +name = "getrandom" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c4567c8db10ae91089c99af84c68c38da3ec2f087c3f82960bcdbf3656b6f4d7" +dependencies = [ + "cfg-if", + "libc", + "wasi", +] + [[package]] name = "hashbrown" -version = "0.14.5" +version = "0.15.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1" +checksum = "3a9bfc1af68b1726ea47d3d5109de126281def866b33970e10fbab11b5dafab3" + +[[package]] +name = "hex" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" [[package]] name = "increment" version = "0.1.0" dependencies = [ "calimero-sdk", + "calimero-storage", ] +[[package]] +name = "indenter" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ce23b50ad8242c51a442f3ff322d56b02f08852c77e4c0b4d3fd684abc89c683" + [[package]] name = "indexmap" -version = "2.2.6" +version = "2.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "168fb715dda47215e360912c096649d23d58bf392ac62f73919e831745e40f26" +checksum = "707907fe3c25f5424cce2cb7e1cbcafee6bdbe735ca90ef77c29e84591e5b9da" dependencies = [ "equivalent", "hashbrown", @@ -97,6 +218,12 @@ version = "1.0.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "49f1f14873335454500d59611f1cf4a4b0f786f9ac11f4312a78e4cf2566695b" +[[package]] +name = "libc" +version = "0.2.161" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e9489c2807c139ffd9c1794f4af0ebe86a828db53ecdc7fea2111d0fed085d1" + [[package]] name = "memchr" version = "2.7.4" @@ -105,15 +232,24 @@ checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" [[package]] name = "once_cell" -version = "1.19.0" +version = "1.20.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92" +checksum = "1261fe7e33c73b354eab43b1273a57c8f967d0391e80353e51f764ac02cf6775" [[package]] -name = "prettyplease" +name = "ppv-lite86" version = "0.2.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5f12335488a2f3b0a83b14edad48dca9879ce89b2edd10e80237e4e852dd645e" +checksum = "77957b295656769bb8ad2b6a6b09d897d94f05c41b069aede1fcdaa675eaea04" +dependencies = [ + "zerocopy", +] + +[[package]] +name = "prettyplease" +version = "0.2.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "64d1ec885c64d0457d564db4ec299b2dae3f9c02808b8ad9c3a089c591b18033" dependencies = [ "proc-macro2", "syn", @@ -121,9 +257,9 @@ dependencies = [ [[package]] name = "proc-macro-crate" -version = "3.1.0" +version = "3.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6d37c51ca738a55da99dc0c4a34860fd675453b8b36209178c2249bb13651284" +checksum = "8ecf48c7ca261d60b74ab1a7b20da18bede46776b2e55535cb958eb595c5fa7b" dependencies = [ "toml_edit", ] @@ -153,22 +289,52 @@ dependencies = [ [[package]] name = "proc-macro2" -version = "1.0.85" +version = "1.0.89" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "22244ce15aa966053a896d1accb3a6e68469b97c7f33f284b99f0d576879fc23" +checksum = "f139b0662de085916d1fb67d2b4169d1addddda1919e696f3252b740b629986e" dependencies = [ "unicode-ident", ] [[package]] name = "quote" -version = "1.0.36" +version = "1.0.37" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0fa76aaf39101c457836aec0ce2316dbdc3ab723cdda1c6bd4e6ad4208acaca7" +checksum = "b5b9d34b8991d19d98081b46eacdd8eb58c6f2b201139f7c5f643cc155a633af" dependencies = [ "proc-macro2", ] +[[package]] +name = "rand" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" +dependencies = [ + "libc", + "rand_chacha", + "rand_core", +] + +[[package]] +name = "rand_chacha" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" +dependencies = [ + "ppv-lite86", + "rand_core", +] + +[[package]] +name = "rand_core" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" +dependencies = [ + "getrandom", +] + [[package]] name = "ryu" version = "1.0.18" @@ -177,18 +343,18 @@ checksum = "f3cb5ba0dc43242ce17de99c180e96db90b235b8a9fdc9543c96d2209116bd9f" [[package]] name = "serde" -version = "1.0.203" +version = "1.0.214" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7253ab4de971e72fb7be983802300c30b5a7f0c2e56fab8abfc6a214307c0094" +checksum = "f55c3193aca71c12ad7890f1785d2b73e1b9f63a0bbc353c08ef26fe03fc56b5" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" -version = "1.0.203" +version = "1.0.214" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "500cbc0ebeb6f46627f50f3f5811ccf6bf00643be300b4c3eabc0ef55dc5b5ba" +checksum = "de523f781f095e28fa605cdce0f8307e451cc0fd14e2eb4cd2e98a355b147766" dependencies = [ "proc-macro2", "quote", @@ -197,20 +363,32 @@ dependencies = [ [[package]] name = "serde_json" -version = "1.0.117" +version = "1.0.132" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "455182ea6142b14f93f4bc5320a2b31c1f266b66a4a5c858b013302a5d8cbfc3" +checksum = "d726bfaff4b320266d395898905d0eba0345aae23b54aee3a737e260fd46db03" dependencies = [ "itoa", + "memchr", "ryu", "serde", ] +[[package]] +name = "sha2" +version = "0.10.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "793db75ad2bcafc3ffa7c68b215fee268f537982cd901d132f89c6343f3a3dc8" +dependencies = [ + "cfg-if", + "cpufeatures", + "digest", +] + [[package]] name = "syn" -version = "2.0.66" +version = "2.0.87" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c42f3f41a2de00b01c0aaad383c5a45241efc8b2d1eda5661812fda5f3cdcff5" +checksum = "25aa4ce346d03a6dcd68dd8b4010bcb74e54e62c90c573f394c46eae99aba32d" dependencies = [ "proc-macro2", "quote", @@ -231,18 +409,18 @@ dependencies = [ [[package]] name = "thiserror" -version = "1.0.61" +version = "1.0.68" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c546c80d6be4bc6a00c0f01730c08df82eaa7a7a61f11d656526506112cc1709" +checksum = "02dd99dc800bbb97186339685293e1cc5d9df1f8fae2d0aecd9ff1c77efea892" dependencies = [ "thiserror-impl", ] [[package]] name = "thiserror-impl" -version = "1.0.61" +version = "1.0.68" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "46c3384250002a6d5af4d114f2845d37b57521033f30d5c3f46c4d70e1197533" +checksum = "a7c61ec9a6f64d2793d8a45faba21efbe3ced62a886d44c36a009b2b519b4c7e" dependencies = [ "proc-macro2", "quote", @@ -251,38 +429,71 @@ dependencies = [ [[package]] name = "toml_datetime" -version = "0.6.6" +version = "0.6.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4badfd56924ae69bcc9039335b2e017639ce3f9b001c393c1b2d1ef846ce2cbf" +checksum = "0dd7358ecb8fc2f8d014bf86f6f638ce72ba252a2c3a2572f2a795f1d23efb41" [[package]] name = "toml_edit" -version = "0.21.1" +version = "0.22.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6a8534fd7f78b5405e860340ad6575217ce99f38d4d5c8f2442cb5ecb50090e1" +checksum = "4ae48d6208a266e853d946088ed816055e556cc6028c5e8e2b84d9fa5dd7c7f5" dependencies = [ "indexmap", "toml_datetime", "winnow", ] +[[package]] +name = "typenum" +version = "1.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42ff0bf0c66b8238c6f3b578df37d0b7848e55df8577b3f74f92a69acceeb825" + [[package]] name = "unicode-ident" -version = "1.0.12" +version = "1.0.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" +checksum = "e91b56cd4cadaeb79bbf1a5645f6b4f8dc5bde8834ad5894a8db35fda9efa1fe" [[package]] name = "version_check" -version = "0.9.4" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" + +[[package]] +name = "wasi" +version = "0.11.0+wasi-snapshot-preview1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" +checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" [[package]] name = "winnow" -version = "0.5.40" +version = "0.6.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f593a95398737aeed53e489c785df13f3618e41dbcd6718c6addbf1395aa6876" +checksum = "36c1fec1a2bb5866f07c25f68c26e565c4c200aebb96d7e55710c19d3e8ac49b" dependencies = [ "memchr", ] + +[[package]] +name = "zerocopy" +version = "0.7.35" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b9b4fd18abc82b8136838da5d50bae7bdea537c574d8dc1a34ed098d6c166f0" +dependencies = [ + "byteorder", + "zerocopy-derive", +] + +[[package]] +name = "zerocopy-derive" +version = "0.7.35" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fa4f8080344d4671fb4e831a13ad1e68092748387dfc4f55e356242fae12ce3e" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] diff --git a/logic/Cargo.toml b/logic/Cargo.toml index 581a08e..428688f 100644 --- a/logic/Cargo.toml +++ b/logic/Cargo.toml @@ -8,7 +8,12 @@ edition = "2021" crate-type = ["cdylib"] [dependencies] -calimero-sdk = { git = "https://github.com/calimero-network/core", branch = "master" } +#calimero-sdk = { git = "https://github.com/calimero-network/core", branch = "master" } +#calimero-storage = { git = "https://github.com/calimero-network/core", branch = "master" } + +calimero-sdk = { path = "../../core/crates/sdk" } +calimero-storage = { path = "../../core/crates/storage" } + [profile.app-release] inherits = "release" diff --git a/logic/src/lib.rs b/logic/src/lib.rs index 23de246..1c257ad 100644 --- a/logic/src/lib.rs +++ b/logic/src/lib.rs @@ -1,44 +1,143 @@ -use calimero_sdk::{ - app, - borsh::{BorshDeserialize, BorshSerialize}, - env, -}; +use calimero_sdk::borsh::{BorshDeserialize, BorshSerialize}; +use calimero_sdk::env::{self}; +use calimero_sdk::types::Error; +use calimero_sdk::{app, serde}; +use calimero_storage::collections::UnorderedMap; +use calimero_storage::entities::Element; +use calimero_storage::AtomicUnit; +use serde::{Deserialize, Serialize}; + +#[derive(Clone, Debug, PartialEq, PartialOrd, Deserialize)] +#[serde(crate = "calimero_sdk::serde")] +pub struct CreateProposalRequest { + proposal_id: String, + author: String, +} + +#[derive(Clone, Debug, PartialEq, PartialOrd, Deserialize)] +#[serde(crate = "calimero_sdk::serde", rename_all = "camelCase")] +pub struct GetProposalMessagesRequest { + proposal_id: String, +} + +#[derive(Clone, Debug, PartialEq, PartialOrd, Deserialize)] +#[serde(crate = "calimero_sdk::serde", rename_all = "camelCase")] +pub struct SendProposalMessageRequest { + proposal_id: String, + message: Message, +} #[app::event] pub enum Event { - Increased(u32), - Reset, + ProposalCreated(), } #[app::state(emits = Event)] -#[derive(Default, BorshDeserialize, BorshSerialize)] -#[borsh(crate = "calimero_sdk::borsh")] +#[derive(AtomicUnit, Clone, Debug, PartialEq, PartialOrd)] +#[root] +#[type_id(1)] pub struct AppState { count: u32, + #[storage] + storage: Element, + + messages: UnorderedMap>, +} + +#[derive( + Clone, Debug, PartialEq, PartialOrd, BorshSerialize, BorshDeserialize, Serialize, Deserialize, +)] +#[borsh(crate = "calimero_sdk::borsh")] +#[serde(crate = "calimero_sdk::serde")] +pub struct Message { + id: String, + proposal_id: String, + author: String, + text: String, + created_at: String, } #[app::logic] impl AppState { #[app::init] pub fn init() -> AppState { - AppState::default() + AppState { + count: 0, + storage: Element::root(), + messages: UnorderedMap::new().unwrap(), + } } - pub fn get_count(&self) -> u32 { - env::log("Get counter"); - self.count + pub fn create_new_proposal(&mut self, _request: CreateProposalRequest) -> Result { + println!("Create new proposal: {:?}", _request); + let account_id = calimero_sdk::env::ext::AccountId("cali.near".to_string()); + let amount = 1; + let proposal_id = Self::external() + .propose() + .transfer(account_id, amount) + .send(); + + println!("Create new proposal with id: {:?}", proposal_id); + + Ok(true) + } + + pub fn approve_proposal(&mut self, _proposal_id: env::ext::ProposalId) -> Result { + println!("Approve proposal: {:?}", _proposal_id); + // Self::external() + Ok(true) } - pub fn increase_count(&mut self, count: u32) -> u32 { - env::log(&format!("Increasing counter by {:?}", count)); - self.count = self.count + count; - app::emit!(Event::Increased(self.count)); - self.count + // Messages (discussion) + pub fn get_proposal_messages( + &self, + // request: GetProposalMessagesRequest, I cannot to this?? + proposal_id: String, + ) -> Result, Error> { + let proposal_id = env::ext::ProposalId(Self::string_to_u8_32(proposal_id.as_str())); + + println!("Get messages for proposal: {:?}", proposal_id); + + let res = &self.messages.get(&proposal_id).unwrap(); + println!("Messages: {:?}", res); + + match res { + Some(messages) => Ok(messages.clone()), + None => Ok(vec![]), + } } - pub fn reset(&mut self) { - env::log("Reset counter"); - self.count = 0; - app::emit!(Event::Reset); + pub fn send_proposal_messages( + &mut self, + // request: SendProposalMessageRequest, I cannot to this?? How to use camelCase? + proposal_id: String, + message: Message, + ) -> Result { + let proposal_id = env::ext::ProposalId(Self::string_to_u8_32(proposal_id.as_str())); + + println!("Send message to proposal: {:?}", proposal_id); + let proposal_messages = self.messages.get(&proposal_id).unwrap(); + match proposal_messages { + Some(mut messages) => { + messages.push(message); + self.messages.insert(proposal_id, messages)?; + } + None => { + let messages = vec![message]; + self.messages.insert(proposal_id, messages)?; + } + } + Ok(true) + } + + fn string_to_u8_32(s: &str) -> [u8; 32] { + let mut array = [0u8; 32]; // Initialize array with 32 zeroes + let bytes = s.as_bytes(); // Convert the string to bytes + + // Copy up to 32 bytes from the string slice into the array + let len = bytes.len().min(32); + array[..len].copy_from_slice(&bytes[..len]); + + array } } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index ab18ab3..f5d8dce 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -7,6 +7,10 @@ settings: importers: .: + dependencies: + bs58: + specifier: ^6.0.0 + version: 6.0.0 devDependencies: husky: specifier: ^9.0.11 @@ -2576,6 +2580,9 @@ packages: base-x@4.0.0: resolution: {integrity: sha512-FuwxlW4H5kh37X/oW59pwTzzTKRzfrrQwhmyspRM7swOEZcHtDZSCt45U6oKgtuFE+WYPblePMVIPR4RZrh/hw==} + base-x@5.0.0: + resolution: {integrity: sha512-sMW3VGSX1QWVFA6l8U62MLKz29rRfpTlYdCqLdpLo1/Yd4zZwSbnUaDfciIAowAqvq7YFnWq9hrhdg1KYgc1lQ==} + base64-js@1.5.1: resolution: {integrity: sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==} @@ -2659,6 +2666,9 @@ packages: bs58@5.0.0: resolution: {integrity: sha512-r+ihvQJvahgYT50JD05dyJNKlmmSlMoOGwn1lCcEzanPglg7TxYjioQUYehQ9mAR/+hOSd2jRc/Z2y5UxBymvQ==} + bs58@6.0.0: + resolution: {integrity: sha512-PD0wEnEYg6ijszw/u8s+iI3H17cTymlrwkKhDhPZq+Sokl3AU4htyBFTjAeNAlCCmg0f53g6ih3jATyCKftTfw==} + bser@2.1.1: resolution: {integrity: sha512-gQxTNE/GAfIIrmHLUE3oJyp5FO6HRBfhjnw4/wMmA63ZGDJnWBmgY/lyQBpnDUkGmAhbSe39tx2d/iTOAfglwQ==} @@ -9932,6 +9942,8 @@ snapshots: base-x@4.0.0: {} + base-x@5.0.0: {} + base64-js@1.5.1: {} bech32@1.1.4: {} @@ -10050,6 +10062,10 @@ snapshots: dependencies: base-x: 4.0.0 + bs58@6.0.0: + dependencies: + base-x: 5.0.0 + bser@2.1.1: dependencies: node-int64: 0.4.0