├── .eslintrc.yml ├── .github ├── ISSUE_TEMPLATE │ ├── bug_report.md │ ├── feature_request.md │ └── improvement.md ├── actions │ ├── contributions │ │ └── action.yml │ └── style │ │ └── action.yml ├── pull_request_template.md └── workflows │ ├── build_node_ui.yml │ ├── contributions.yml │ ├── e2e_tests.yml │ ├── new-issue-or-pr.yml │ ├── release.yml │ ├── rust.yml │ └── stale.yml ├── .gitignore ├── .husky └── pre-commit ├── .prettierignore ├── .taplo.toml ├── CHANGELOG.md ├── CODE_OF_CONDUCT.md ├── CONTRIBUTING.md ├── CONTRIBUTORS.md ├── COPYRIGHT ├── Cargo.lock ├── Cargo.toml ├── Dockerfile ├── LICENSE.md ├── NOTICE ├── README.mdx ├── apps ├── gen-ext │ ├── Cargo.toml │ ├── build.sh │ └── src │ │ └── lib.rs ├── kv-store │ ├── Cargo.toml │ ├── build.sh │ └── src │ │ └── lib.rs ├── only-peers │ ├── Cargo.toml │ ├── build.sh │ └── src │ │ └── lib.rs └── visited │ ├── Cargo.toml │ ├── build.sh │ └── src │ └── lib.rs ├── contracts ├── icp │ ├── .gitignore │ ├── context-config │ │ ├── .env │ │ ├── Cargo.toml │ │ ├── build.sh │ │ ├── deploy_devnet.sh │ │ ├── dfx.json │ │ ├── res │ │ │ └── calimero_context_config_icp.did │ │ ├── src │ │ │ ├── guard.rs │ │ │ ├── lib.rs │ │ │ ├── mutate.rs │ │ │ ├── query.rs │ │ │ └── sys.rs │ │ └── tests │ │ │ └── integration.rs │ └── context-proxy │ │ ├── Cargo.toml │ │ ├── build.sh │ │ ├── build_contracts.sh │ │ ├── dfx.json │ │ ├── mock │ │ └── external │ │ │ ├── Cargo.toml │ │ │ ├── build.sh │ │ │ ├── res │ │ │ └── calimero_mock_external_icp.did │ │ │ └── src │ │ │ └── lib.rs │ │ ├── res │ │ └── calimero_context_proxy_icp.did │ │ ├── src │ │ ├── lib.rs │ │ ├── mutate.rs │ │ ├── query.rs │ │ └── sys.rs │ │ └── tests │ │ └── integration.rs ├── near │ ├── context-config │ │ ├── Cargo.toml │ │ ├── build.sh │ │ ├── src │ │ │ ├── guard.rs │ │ │ ├── lib.rs │ │ │ ├── mutate.rs │ │ │ ├── query.rs │ │ │ ├── sys.rs │ │ │ └── sys │ │ │ │ ├── migrations.rs │ │ │ │ └── migrations │ │ │ │ ├── 01_guard_revisions.rs │ │ │ │ └── 02_nonces.rs │ │ └── tests │ │ │ ├── migrations.rs │ │ │ └── sandbox.rs │ ├── context-proxy │ │ ├── Cargo.toml │ │ ├── build-test-deps.sh │ │ ├── build.sh │ │ ├── src │ │ │ ├── ext_config.rs │ │ │ ├── lib.rs │ │ │ └── mutate.rs │ │ ├── test.sh │ │ └── tests │ │ │ ├── common │ │ │ ├── config_helper.rs │ │ │ ├── counter_helper.rs │ │ │ ├── mod.rs │ │ │ └── proxy_lib_helper.rs │ │ │ └── sandbox.rs │ ├── registry │ │ ├── Cargo.toml │ │ ├── README.md │ │ ├── build.sh │ │ ├── deploy.sh │ │ ├── src │ │ │ └── lib.rs │ │ └── tests │ │ │ ├── mock.rs │ │ │ └── sandbox.rs │ └── test-counter │ │ ├── Cargo.toml │ │ ├── build.sh │ │ ├── src │ │ └── lib.rs │ │ ├── test.sh │ │ └── tests │ │ └── sandbox.rs └── stellar │ ├── context-config │ ├── .gitignore │ ├── Cargo.toml │ ├── Makefile │ ├── build.sh │ ├── src │ │ ├── guard.rs │ │ ├── lib.rs │ │ ├── mutate.rs │ │ ├── query.rs │ │ └── sys.rs │ ├── test_snapshots │ │ └── test │ │ │ ├── test_add_context.1.json │ │ │ ├── test_application_update.1.json │ │ │ ├── test_capability_management.1.json │ │ │ ├── test_member_management.1.json │ │ │ └── test_query_endpoints.1.json │ └── tests │ │ └── integration.rs │ └── context-proxy │ ├── .gitignore │ ├── Cargo.toml │ ├── Makefile │ ├── build.sh │ ├── build_contracts.sh │ ├── mock_external │ ├── .gitignore │ ├── Cargo.toml │ ├── Makefile │ ├── build.sh │ └── src │ │ ├── lib.rs │ │ └── test.rs │ ├── src │ ├── lib.rs │ ├── mutate.rs │ ├── query.rs │ └── sys.rs │ ├── test_snapshots │ └── test │ │ ├── test_execute_proposal_external_call_deposit.1.json │ │ ├── test_execute_proposal_external_call_no_deposit.1.json │ │ ├── test_execute_proposal_set_active_proposals_limit.1.json │ │ ├── test_execute_proposal_set_num_approvals.1.json │ │ ├── test_execute_proposal_transfer.1.json │ │ └── test_proposal_limits_and_deletion.1.json │ └── tests │ └── integration.rs ├── crates ├── config │ ├── Cargo.toml │ └── src │ │ └── lib.rs ├── context │ ├── Cargo.toml │ ├── README.md │ ├── config │ │ ├── Cargo.toml │ │ └── src │ │ │ ├── client.rs │ │ │ ├── client │ │ │ ├── config.rs │ │ │ ├── env.rs │ │ │ ├── env │ │ │ │ ├── config.rs │ │ │ │ ├── config │ │ │ │ │ ├── mutate.rs │ │ │ │ │ ├── mutate │ │ │ │ │ │ └── methods.rs │ │ │ │ │ ├── query.rs │ │ │ │ │ ├── query │ │ │ │ │ │ ├── application.rs │ │ │ │ │ │ ├── application_revision.rs │ │ │ │ │ │ ├── fetch_nonce.rs │ │ │ │ │ │ ├── has_member.rs │ │ │ │ │ │ ├── members.rs │ │ │ │ │ │ ├── members_revision.rs │ │ │ │ │ │ ├── privileges.rs │ │ │ │ │ │ └── proxy_contract.rs │ │ │ │ │ ├── types.rs │ │ │ │ │ └── types │ │ │ │ │ │ └── starknet.rs │ │ │ │ ├── proxy.rs │ │ │ │ └── proxy │ │ │ │ │ ├── mutate.rs │ │ │ │ │ ├── mutate │ │ │ │ │ └── methods.rs │ │ │ │ │ ├── query.rs │ │ │ │ │ ├── query │ │ │ │ │ ├── active_proposals.rs │ │ │ │ │ ├── context_storage_entries.rs │ │ │ │ │ ├── context_variable.rs │ │ │ │ │ ├── proposal.rs │ │ │ │ │ ├── proposal_approvals.rs │ │ │ │ │ ├── proposal_approvers.rs │ │ │ │ │ └── proposals.rs │ │ │ │ │ ├── types.rs │ │ │ │ │ └── types │ │ │ │ │ └── starknet.rs │ │ │ ├── protocol.rs │ │ │ ├── protocol │ │ │ │ ├── icp.rs │ │ │ │ ├── near.rs │ │ │ │ ├── starknet.rs │ │ │ │ └── stellar.rs │ │ │ ├── relayer.rs │ │ │ ├── transport.rs │ │ │ └── utils.rs │ │ │ ├── icp.rs │ │ │ ├── icp │ │ │ ├── repr.rs │ │ │ └── types.rs │ │ │ ├── lib.rs │ │ │ ├── repr.rs │ │ │ ├── stellar.rs │ │ │ ├── stellar │ │ │ └── stellar_types.rs │ │ │ └── types.rs │ └── src │ │ ├── config.rs │ │ └── lib.rs ├── crypto │ ├── Cargo.toml │ └── src │ │ └── lib.rs ├── meroctl │ ├── Cargo.toml │ └── src │ │ ├── cli.rs │ │ ├── cli │ │ ├── app.rs │ │ ├── app │ │ │ ├── get.rs │ │ │ ├── install.rs │ │ │ └── list.rs │ │ ├── bootstrap.rs │ │ ├── bootstrap │ │ │ └── start.rs │ │ ├── call.rs │ │ ├── context.rs │ │ ├── context │ │ │ ├── create.rs │ │ │ ├── delete.rs │ │ │ ├── get.rs │ │ │ ├── invite.rs │ │ │ ├── join.rs │ │ │ ├── list.rs │ │ │ ├── update.rs │ │ │ └── watch.rs │ │ ├── identity.rs │ │ ├── identity │ │ │ └── generate.rs │ │ ├── peers.rs │ │ ├── proxy.rs │ │ └── proxy │ │ │ └── get.rs │ │ ├── common.rs │ │ ├── defaults.rs │ │ ├── main.rs │ │ └── output.rs ├── merod │ ├── Cargo.toml │ ├── gen_localnet_configs.sh │ └── src │ │ ├── cli.rs │ │ ├── cli │ │ ├── config.rs │ │ ├── init.rs │ │ ├── relay.rs │ │ └── run.rs │ │ ├── defaults.rs │ │ └── main.rs ├── network │ ├── Cargo.toml │ ├── README.md │ └── src │ │ ├── client.rs │ │ ├── config.rs │ │ ├── discovery.rs │ │ ├── discovery │ │ └── state.rs │ │ ├── events.rs │ │ ├── events │ │ ├── dcutr.rs │ │ ├── gossipsub.rs │ │ ├── identify.rs │ │ ├── kad.rs │ │ ├── mdns.rs │ │ ├── ping.rs │ │ ├── relay.rs │ │ └── rendezvous.rs │ │ ├── lib.rs │ │ ├── stream.rs │ │ ├── stream │ │ └── codec.rs │ │ ├── tests │ │ ├── discovery │ │ │ └── state.rs │ │ └── stream │ │ │ └── codec.rs │ │ └── types.rs ├── node-primitives │ ├── Cargo.toml │ └── src │ │ └── lib.rs ├── node │ ├── Cargo.toml │ ├── README.md │ └── src │ │ ├── interactive_cli.rs │ │ ├── interactive_cli │ │ ├── applications.rs │ │ ├── call.rs │ │ ├── context.rs │ │ ├── identity.rs │ │ ├── peers.rs │ │ ├── state.rs │ │ └── store.rs │ │ ├── lib.rs │ │ ├── runtime_compat.rs │ │ ├── sync.rs │ │ ├── sync │ │ ├── blobs.rs │ │ ├── key.rs │ │ └── state.rs │ │ └── types.rs ├── primitives │ ├── Cargo.toml │ └── src │ │ ├── application.rs │ │ ├── blobs.rs │ │ ├── common.rs │ │ ├── context.rs │ │ ├── events.rs │ │ ├── hash.rs │ │ ├── identity.rs │ │ ├── lib.rs │ │ ├── reflect.rs │ │ └── tests │ │ └── hash.rs ├── runtime │ ├── Cargo.toml │ ├── README.md │ ├── examples │ │ ├── demo.rs │ │ ├── fetch.rs │ │ └── rps.rs │ └── src │ │ ├── constraint.rs │ │ ├── errors.rs │ │ ├── lib.rs │ │ ├── logic.rs │ │ ├── logic │ │ ├── errors.rs │ │ ├── imports.rs │ │ └── registers.rs │ │ ├── memory.rs │ │ ├── store.rs │ │ └── tests │ │ └── errors.rs ├── sdk │ ├── Cargo.toml │ ├── design-guide.md │ ├── libs │ │ └── near │ │ │ ├── Cargo.toml │ │ │ └── src │ │ │ ├── error.rs │ │ │ ├── jsonrpc.rs │ │ │ ├── lib.rs │ │ │ ├── query.rs │ │ │ ├── types.rs │ │ │ └── views.rs │ ├── macros │ │ ├── Cargo.toml │ │ ├── design-guide.md │ │ └── src │ │ │ ├── errors.rs │ │ │ ├── event.rs │ │ │ ├── items.rs │ │ │ ├── lib.rs │ │ │ ├── logic.rs │ │ │ ├── logic │ │ │ ├── arg.rs │ │ │ ├── method.rs │ │ │ ├── ty.rs │ │ │ └── utils.rs │ │ │ ├── macros.rs │ │ │ ├── reserved.rs │ │ │ ├── sanitizer.rs │ │ │ ├── state.rs │ │ │ └── tests │ │ │ └── sanitizer.rs │ ├── src │ │ ├── env.rs │ │ ├── env │ │ │ └── ext.rs │ │ ├── event.rs │ │ ├── lib.rs │ │ ├── returns.rs │ │ ├── state.rs │ │ ├── sys.rs │ │ ├── sys │ │ │ ├── types.rs │ │ │ └── types │ │ │ │ ├── bool.rs │ │ │ │ ├── buffer.rs │ │ │ │ ├── event.rs │ │ │ │ ├── location.rs │ │ │ │ ├── pointer.rs │ │ │ │ └── register.rs │ │ └── types.rs │ └── tests │ │ ├── macros.rs │ │ └── macros │ │ ├── invalid_args.rs │ │ ├── invalid_generics.rs │ │ ├── invalid_generics.stderr │ │ ├── invalid_methods.rs │ │ ├── invalid_methods.stderr │ │ ├── invalid_receivers.rs │ │ ├── invalid_receivers.stderr │ │ ├── invalid_state.rs │ │ ├── valid_args.rs │ │ ├── valid_generics.rs │ │ ├── valid_receivers.rs │ │ └── valid_receivers.stderr ├── server-primitives │ ├── Cargo.toml │ └── src │ │ ├── admin.rs │ │ ├── jsonrpc.rs │ │ ├── lib.rs │ │ └── ws.rs ├── server │ ├── Cargo.toml │ ├── README.md │ └── src │ │ ├── admin.rs │ │ ├── admin │ │ ├── handlers.rs │ │ ├── handlers │ │ │ ├── add_client_key.rs │ │ │ ├── applications.rs │ │ │ ├── applications │ │ │ │ ├── get_application.rs │ │ │ │ ├── install_application.rs │ │ │ │ ├── install_dev_application.rs │ │ │ │ ├── list_applications.rs │ │ │ │ └── uninstall_application.rs │ │ │ ├── challenge.rs │ │ │ ├── context.rs │ │ │ ├── context │ │ │ │ ├── create_context.rs │ │ │ │ ├── delete_context.rs │ │ │ │ ├── get_context.rs │ │ │ │ ├── get_context_client_keys.rs │ │ │ │ ├── get_context_identities.rs │ │ │ │ ├── get_context_storage.rs │ │ │ │ ├── get_contexts.rs │ │ │ │ ├── invite_to_context.rs │ │ │ │ ├── join_context.rs │ │ │ │ └── update_context_application.rs │ │ │ ├── did.rs │ │ │ ├── identity.rs │ │ │ ├── identity │ │ │ │ └── generate_context_identity.rs │ │ │ ├── peers.rs │ │ │ ├── proposals.rs │ │ │ └── root_keys.rs │ │ ├── service.rs │ │ ├── storage.rs │ │ ├── storage │ │ │ ├── client_keys.rs │ │ │ ├── did.rs │ │ │ ├── jwt_secret.rs │ │ │ ├── jwt_token.rs │ │ │ ├── root_key.rs │ │ │ └── ssl.rs │ │ ├── utils.rs │ │ └── utils │ │ │ ├── auth.rs │ │ │ ├── auth_tests.rs │ │ │ └── jwt.rs │ │ ├── certificates.rs │ │ ├── config.rs │ │ ├── jsonrpc.rs │ │ ├── jsonrpc │ │ └── execute.rs │ │ ├── lib.rs │ │ ├── middleware.rs │ │ ├── middleware │ │ ├── auth.rs │ │ ├── dev_auth.rs │ │ ├── host.rs │ │ └── jwt.rs │ │ ├── tests │ │ └── verifywalletsignatures │ │ │ └── near.rs │ │ ├── verifywalletsignatures.rs │ │ ├── verifywalletsignatures │ │ ├── icp.rs │ │ ├── near.rs │ │ └── starknet.rs │ │ ├── ws.rs │ │ └── ws │ │ ├── subscribe.rs │ │ └── unsubscribe.rs ├── storage-macros │ ├── Cargo.toml │ └── src │ │ └── lib.rs ├── storage │ ├── Cargo.toml │ ├── README.md │ └── src │ │ ├── address.rs │ │ ├── collections.rs │ │ ├── collections │ │ ├── error.rs │ │ ├── root.rs │ │ ├── unordered_map.rs │ │ ├── unordered_set.rs │ │ └── vector.rs │ │ ├── entities.rs │ │ ├── env.rs │ │ ├── index.rs │ │ ├── integration.rs │ │ ├── interface.rs │ │ ├── lib.rs │ │ ├── store.rs │ │ ├── sync.rs │ │ └── tests │ │ ├── address.rs │ │ ├── common.rs │ │ ├── entities.rs │ │ ├── index.rs │ │ └── interface.rs └── store │ ├── Cargo.toml │ ├── README.md │ ├── blobs │ ├── Cargo.toml │ ├── examples │ │ └── blobstore.rs │ └── src │ │ ├── config.rs │ │ └── lib.rs │ ├── src │ ├── config.rs │ ├── db.rs │ ├── db │ │ ├── memory.rs │ │ ├── memory │ │ │ └── raw.rs │ │ └── rocksdb.rs │ ├── entry.rs │ ├── handle.rs │ ├── iter.rs │ ├── key.rs │ ├── key │ │ ├── application.rs │ │ ├── blobs.rs │ │ ├── component.rs │ │ ├── context.rs │ │ └── generic.rs │ ├── layer.rs │ ├── layer │ │ ├── experiments.rs │ │ ├── read_only.rs │ │ └── temporal.rs │ ├── lib.rs │ ├── slice.rs │ ├── tests │ │ ├── db │ │ │ ├── memory.rs │ │ │ └── rocksdb.rs │ │ └── slice.rs │ ├── tx.rs │ ├── types.rs │ └── types │ │ ├── application.rs │ │ ├── blobs.rs │ │ ├── context.rs │ │ └── generic.rs │ └── tests │ └── rocksdb.rs ├── docker-compose.yml ├── e2e-tests ├── Cargo.toml ├── README.md ├── config │ ├── config.json │ └── scenarios │ │ └── kv-store │ │ └── test.json └── src │ ├── config.rs │ ├── driver.rs │ ├── main.rs │ ├── meroctl.rs │ ├── merod.rs │ ├── output.rs │ ├── protocol.rs │ ├── protocol │ ├── icp.rs │ └── near.rs │ ├── steps.rs │ └── steps │ ├── application_install.rs │ ├── context_create.rs │ ├── context_invite_join.rs │ └── jsonrpc_call.rs ├── node-ui ├── .env ├── .eslint.config.mjs ├── .eslintignore ├── .gitignore ├── .prettierignore ├── build │ ├── 404.html │ ├── assets │ │ ├── calimero-logo-BYRMmiO8.svg │ │ ├── favicon-CrZyDFVy.ico │ │ ├── main-BesWMiQO.css │ │ └── main-BxzzjMgG.js │ ├── index.html │ └── public │ │ └── 404.html ├── custom.d.ts ├── index.html ├── package.json ├── pnpm-lock.yaml ├── prettier.config.cjs ├── public │ └── 404.html ├── src │ ├── App.tsx │ ├── api │ │ ├── dataSource │ │ │ └── NodeDataSource.ts │ │ ├── httpClient.ts │ │ ├── index.ts │ │ ├── nodeApi.ts │ │ └── response.ts │ ├── assets │ │ ├── calimero-logo.svg │ │ ├── crypto-wallet-selector-animation.svg │ │ ├── favicon.ico │ │ ├── icp.svg │ │ ├── login-icon.svg │ │ ├── metamask-icon.svg │ │ ├── near-gif.gif │ │ ├── near-icon.svg │ │ └── starknet-icon.svg │ ├── auth │ │ ├── ed25519.ts │ │ ├── headers.ts │ │ ├── storage.ts │ │ └── types.ts │ ├── components │ │ ├── Navigation.tsx │ │ ├── applications │ │ │ ├── ApplicationRowItem.tsx │ │ │ ├── ApplicationsContent.tsx │ │ │ ├── ApplicationsTable.tsx │ │ │ ├── InstallApplicationCard.tsx │ │ │ ├── InstalledApplicationRowItem.tsx │ │ │ ├── Item.tsx │ │ │ ├── ReleaseItem.tsx │ │ │ └── details │ │ │ │ ├── ApplicationDetailsTable.tsx │ │ │ │ ├── DetailsCard.tsx │ │ │ │ └── ReleaseRowItem.tsx │ │ ├── common │ │ │ ├── ActionDialog.tsx │ │ │ ├── Button.tsx │ │ │ ├── ButtonLight.tsx │ │ │ ├── ContentCard.tsx │ │ │ ├── ListTable.tsx │ │ │ ├── LoaderSpinner.tsx │ │ │ ├── Loading.tsx │ │ │ ├── MenuIconDropdown.tsx │ │ │ ├── OptionsHeader.tsx │ │ │ ├── PageContentWrapper.tsx │ │ │ ├── StatusModal.tsx │ │ │ └── StatusModalItem.tsx │ │ ├── confirmWallet │ │ │ └── RootKeyContainer.tsx │ │ ├── context │ │ │ ├── ContextTable.tsx │ │ │ ├── InvitationRowItem.tsx │ │ │ ├── RowItem.tsx │ │ │ ├── contextDetails │ │ │ │ ├── ClientKeyRowItem.tsx │ │ │ │ ├── ContextTable.tsx │ │ │ │ ├── DetailsCard.tsx │ │ │ │ └── UserRowItem.tsx │ │ │ ├── joinContext │ │ │ │ └── JoinContextCard.tsx │ │ │ └── startContext │ │ │ │ ├── ApplicationsPopup.tsx │ │ │ │ ├── ApplicationsTable.tsx │ │ │ │ ├── RowItem.tsx │ │ │ │ └── StartContextCard.tsx │ │ ├── exportIdentity │ │ │ └── ExportCard.tsx │ │ ├── footer │ │ │ └── Footer.tsx │ │ ├── icp │ │ │ └── Icp.tsx │ │ ├── identity │ │ │ ├── IdentityRowItem.tsx │ │ │ └── IdentityTable.tsx │ │ ├── layout │ │ │ └── FlexLayout.tsx │ │ ├── login │ │ │ ├── ContentWrapper.tsx │ │ │ ├── applicationLogin │ │ │ │ ├── AppLoginPopup.tsx │ │ │ │ ├── CreateAccessTokenStep.tsx │ │ │ │ ├── ListItem.tsx │ │ │ │ ├── SelectContextStep.tsx │ │ │ │ ├── SelectIdentityStep.tsx │ │ │ │ ├── StartContextPopup.tsx │ │ │ │ └── StartContextStep.tsx │ │ │ └── wallets │ │ │ │ └── LoginSelector.tsx │ │ ├── metamask │ │ │ ├── MetamaskRoute.tsx │ │ │ └── MetamaskWallet.tsx │ │ ├── near │ │ │ ├── NearRoute.tsx │ │ │ └── NearWallet.tsx │ │ ├── protectedRoutes │ │ │ └── ProtectedRoute.tsx │ │ ├── publishApplication │ │ │ ├── AddPackageForm.tsx │ │ │ ├── AddReleaseForm.tsx │ │ │ ├── ConnectWalletAccountCard.tsx │ │ │ ├── PublishApplicationTable.tsx │ │ │ ├── UploadAppContent.tsx │ │ │ ├── UploadSwitch.tsx │ │ │ └── addRelease │ │ │ │ └── AddReleaseTable.tsx │ │ ├── setup │ │ │ └── SetupModal.tsx │ │ └── starknet │ │ │ └── StarknetWallet.tsx │ ├── constants │ │ ├── ApplicationsConstants.ts │ │ ├── ContextConstants.ts │ │ └── en.global.json │ ├── context │ │ ├── AppLoginContext.tsx │ │ ├── ServerDownContext.tsx │ │ └── WalletSelectorContext.tsx │ ├── hooks │ │ ├── useIcp.tsx │ │ ├── useMetamask.tsx │ │ ├── useNear.tsx │ │ └── useStarknet.tsx │ ├── main.tsx │ ├── pages │ │ ├── AddRelease.tsx │ │ ├── AddRootKey.tsx │ │ ├── ApplicationDetails.tsx │ │ ├── Applications.tsx │ │ ├── Authenticate.tsx │ │ ├── ContextDetails.tsx │ │ ├── Contexts.tsx │ │ ├── Export.tsx │ │ ├── Icp.tsx │ │ ├── Identity.tsx │ │ ├── InstallApplication.tsx │ │ ├── JoinContext.tsx │ │ ├── Metamask.tsx │ │ ├── Near.tsx │ │ ├── PublishApplication.tsx │ │ ├── Starknet.tsx │ │ ├── StartContext.tsx │ │ └── setup │ │ │ └── index.tsx │ ├── styles │ │ └── index.css │ ├── types │ │ └── context.ts │ └── utils │ │ ├── authHeaders.ts │ │ ├── copyToClipboard.ts │ │ ├── date.ts │ │ ├── displayFunctions.ts │ │ ├── ethWalletType.ts │ │ ├── interpolate.ts │ │ ├── metadata.ts │ │ ├── node.ts │ │ ├── protectedRoute.ts │ │ ├── rootkey.ts │ │ ├── starknetWalletType.ts │ │ ├── storage.ts │ │ └── wallet.ts ├── tsconfig.json ├── vite.config.ts ├── webpack.config.mjs └── workflows │ ├── deploy-react.yml │ └── setup │ └── action.yml ├── package.json ├── pnpm-lock.yaml ├── pnpm-workspace.yaml ├── prettier.config.cjs ├── rust-toolchain.toml ├── rustfmt.toml └── scripts ├── build-all-apps.sh └── test.sh /.eslintrc.yml: -------------------------------------------------------------------------------- 1 | env: 2 | es6: true 3 | node: true 4 | extends: 5 | - "eslint:recommended" 6 | parserOptions: 7 | ecmaVersion: 2020 8 | sourceType: module 9 | rules: 10 | indent: 11 | - error 12 | - 2 13 | linebreak-style: 14 | - error 15 | - unix 16 | quotes: 17 | - error 18 | - single 19 | semi: 20 | - error 21 | - always 22 | no-console: off 23 | globals: 24 | window: true 25 | fetch: true 26 | Headers: true 27 | document: true 28 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Suggest an idea for this project 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | --- 8 | 9 | ## Is your feature request related to a problem? Please describe 10 | 11 | A clear and concise description of what the problem is. Ex. I'm always 12 | frustrated when [...] 13 | 14 | ## Describe the solution you'd like 15 | 16 | A clear and concise description of what you want to happen. 17 | 18 | ## Describe alternatives you've considered 19 | 20 | A clear and concise description of any alternative solutions or features you've 21 | considered. 22 | 23 | ## Additional context 24 | 25 | Add any other context or screenshots about the feature request here. 26 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/improvement.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Improvement 3 | about: Suggest improvement over existing feature 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | --- 8 | 9 | Is your improvement request related to existing solution? Please describe. A 10 | clear and concise description of what the problem with the existing solution is. 11 | Ex. There is a bottleneck when [...] 12 | 13 | Describe the solution you'd like A clear and concise description of what you 14 | want to happen. 15 | 16 | Describe alternatives you've considered A clear and concise description of any 17 | alternative solutions you've considered but not choose. 18 | 19 | Optional additional items Issue default title: Assignees: Labels: 20 | -------------------------------------------------------------------------------- /.github/actions/contributions/action.yml: -------------------------------------------------------------------------------- 1 | name: "Needs Attention Labeler" 2 | description: "Applies 'needs attention' and other labels to PRs or issues" 3 | inputs: 4 | repo-token: 5 | description: "GitHub token with repository permissions" 6 | required: true 7 | response-required-label: 8 | description: "Label to apply when response is required" 9 | required: true 10 | needs-attention-label: 11 | description: "Label to apply when attention is needed" 12 | required: true 13 | outputs: 14 | result: 15 | description: "Result of the labeling action" 16 | value: ${{ steps.needs-attention.outputs.result }} 17 | runs: 18 | using: "composite" 19 | steps: 20 | - name: Checkout repository 21 | uses: actions/checkout@v4 22 | 23 | - name: Apply Needs Attention Label 24 | uses: hramos/needs-attention@v2.0.0 25 | with: 26 | repo-token: ${{ inputs.repo-token }} 27 | response-required-label: ${{ inputs.response-required-label }} 28 | needs-attention-label: ${{ inputs.needs-attention-label }} 29 | id: needs-attention 30 | 31 | - name: Set output result 32 | shell: bash 33 | run: echo "result=${{ steps.needs-attention.outputs.result }}" >> $GITHUB_OUTPUT 34 | -------------------------------------------------------------------------------- /.github/pull_request_template.md: -------------------------------------------------------------------------------- 1 | # [product] short description 2 | 3 | Please include a short description of the change and which issue is fixed. 4 | Please also include relevant motivation and context. List any dependencies that 5 | are required for this change. 6 | 7 | ## Test plan 8 | 9 | Please describe the tests that you ran to verify your changes. Provide 10 | instructions so we can reproduce. Is it possible to add a test case to our 11 | end-to-end tests with changes from this PR? Add screenshots or videos for 12 | changes in the user-interface. 13 | 14 | ## Documentation update 15 | 16 | Mention here what part (if any) of public or internal documentation should be 17 | updated because of this PR. Documentation **has to be updated** no later than 18 | **one day** after this PR has been merged. 19 | -------------------------------------------------------------------------------- /.github/workflows/contributions.yml: -------------------------------------------------------------------------------- 1 | name: Apply label on issue or pr comment 2 | 3 | on: 4 | issue_comment: 5 | types: [created] 6 | 7 | jobs: 8 | applyNeedsAttentionLabel: 9 | name: Apply Needs Attention 10 | runs-on: ubuntu-latest 11 | permissions: 12 | contents: read # for actions/checkout to fetch code 13 | issues: write # for hramos/needs-attention to label issues 14 | steps: 15 | - name: Checkout repository 16 | uses: actions/checkout@v4 17 | 18 | - name: Apply Needs Attention Label 19 | id: needs-attention 20 | uses: ./.github/actions/contributions 21 | with: 22 | repo-token: ${{ secrets.GITHUB_TOKEN }} 23 | response-required-label: "waiting-for-author-feedback" 24 | needs-attention-label: "needs-team-review" 25 | - name: Result 26 | run: echo '${{ steps.needs-attention.outputs.result }}' 27 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | data/ 2 | corpus/ 3 | **/res/* 4 | target 5 | target/ 6 | .DS_Store 7 | .idea/ 8 | .vscode/ 9 | 10 | # typescript 11 | *.map 12 | coverage/ 13 | dist/ 14 | lib/ 15 | node_modules/ 16 | certs/ 17 | output/* 18 | -------------------------------------------------------------------------------- /.husky/pre-commit: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | . "$(dirname "$0")/_/husky.sh" 3 | 4 | echo "Running pre-commit hook..." 5 | 6 | # Check for changes in Markdown or MDX files 7 | if git diff --cached --name-only | grep -qE '\.mdx?$'; then 8 | echo "Markdown or MDX files have been changed." 9 | pnpm format:md 10 | fi 11 | 12 | # Check for changes in Rust files 13 | if git diff --cached --name-only | grep -qE '\.rs$'; then 14 | echo "Running checks for the Rust code..." 15 | cargo +nightly fmt 16 | fi 17 | 18 | # Check for changes in the 'node-ui' directory (Next.js app) 19 | if git diff --cached --name-only | grep -q '^node-ui/'; then 20 | echo "Running checks for the node-ui (Next.js app)..." 21 | (cd node-ui && pnpm prettier && pnpm lint:fix) 22 | fi 23 | -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | # Ignore artifacts: 2 | data 3 | build 4 | target 5 | coverage 6 | node_modules -------------------------------------------------------------------------------- /.taplo.toml: -------------------------------------------------------------------------------- 1 | [formatting] 2 | column_width = 120 3 | -------------------------------------------------------------------------------- /CONTRIBUTORS.md: -------------------------------------------------------------------------------- 1 | # Contributors to Calimero 2 | 3 | [Apache license]: https://www.apache.org/licenses/LICENSE-2.0 4 | [DCO]: https://developercertificate.org/ 5 | 6 | We contributors to Calimero: 7 | 8 | - License all our contributions to the project under the [Apache License, 9 | Version 2.0][Apache license]. 10 | 11 | - Have the legal rights to license our contributions ourselves, or get 12 | permission to license them from our employers, clients, or others who may have 13 | them. 14 | 15 | - Sign off on our commits with the [Developer Certificate of Origin (DCO) 16 | Version 1.1][DCO]. 17 | 18 | - Previously we added our names and GitHub handles to this `CONTRIBUTORS.md` 19 | file. We leave these names here to record the commits that came before. 20 | 21 | --- 22 | -------------------------------------------------------------------------------- /COPYRIGHT: -------------------------------------------------------------------------------- 1 | Copyrights in the Calimero software are retained by their contributors. 2 | No copyright assignment is required to contribute to Calimero software. 3 | 4 | When we refer to the 'Calimero software authors', we mean anyone who 5 | has contributed to this repository. 6 | 7 | Unless otherwise noted, such as with a LICENSE file in a directory, or a 8 | specific copyright notice on a file, all files in this repository are 9 | licensed under the Apache 2.0 license. 10 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | # Use an official Rust image as the base 2 | FROM rust:latest 3 | 4 | # Install system dependencies 5 | RUN apt-get update && apt-get install -y \ 6 | zlib1g-dev \ 7 | libsnappy-dev \ 8 | libbz2-dev \ 9 | liblz4-dev \ 10 | libzstd-dev \ 11 | clang \ 12 | libclang-dev \ 13 | curl \ 14 | build-essential \ 15 | pkg-config \ 16 | jq 17 | 18 | # Install Node.js (version 20) and pnpm 19 | RUN curl -fsSL https://deb.nodesource.com/setup_20.x | bash - && \ 20 | apt-get install -y nodejs && \ 21 | npm install -g pnpm 22 | 23 | # Set the working directory 24 | WORKDIR /app 25 | 26 | # Copy only necessary files for building dependencies 27 | COPY Cargo.toml Cargo.lock ./ 28 | COPY crates ./crates 29 | COPY contracts ./contracts 30 | COPY apps ./apps 31 | COPY node-ui ./node-ui 32 | 33 | # Build the node UI 34 | WORKDIR /app/node-ui 35 | RUN pnpm install && pnpm run build 36 | 37 | # Build the merod binary 38 | WORKDIR /app 39 | RUN cargo build --release -p merod 40 | 41 | # Set the binary as the entrypoint 42 | ENTRYPOINT ["/app/target/release/merod"] 43 | 44 | # Default command (can be overridden in docker-compose) 45 | CMD ["--help"] 46 | -------------------------------------------------------------------------------- /NOTICE: -------------------------------------------------------------------------------- 1 | Calimero 2 | Copyright the various Calimmero software authors. 3 | 4 | The initial developer of the software is Calimero Ltd. (https://calimero.network). 5 | Copyright 2024 Calimero Ltd. All Rights Reserved. 6 | -------------------------------------------------------------------------------- /apps/gen-ext/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "gen-ext" 3 | version = "0.1.0" 4 | authors.workspace = true 5 | edition.workspace = true 6 | repository.workspace = true 7 | license.workspace = true 8 | 9 | [lib] 10 | crate-type = ["cdylib"] 11 | 12 | [dependencies] 13 | calimero-sdk.workspace = true 14 | calimero-sdk-near.workspace = true 15 | calimero-storage.workspace = true 16 | -------------------------------------------------------------------------------- /apps/gen-ext/build.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -e 3 | 4 | cd "$(dirname $0)" 5 | 6 | TARGET="${CARGO_TARGET_DIR:-../../target}" 7 | 8 | rustup target add wasm32-unknown-unknown 9 | 10 | cargo build --target wasm32-unknown-unknown --profile app-release 11 | 12 | mkdir -p res 13 | 14 | cp $TARGET/wasm32-unknown-unknown/app-release/gen_ext.wasm ./res/ 15 | 16 | if command -v wasm-opt > /dev/null; then 17 | wasm-opt -Oz ./res/gen_ext.wasm -o ./res/gen_ext.wasm 18 | fi 19 | -------------------------------------------------------------------------------- /apps/gen-ext/src/lib.rs: -------------------------------------------------------------------------------- 1 | use calimero_sdk::app; 2 | use calimero_sdk::borsh::{BorshDeserialize, BorshSerialize}; 3 | use calimero_sdk_near::query::RpcQueryRequest; 4 | use calimero_sdk_near::views::QueryRequest; 5 | use calimero_sdk_near::Client; 6 | 7 | #[app::state] 8 | #[derive(BorshDeserialize, BorshSerialize, Default)] 9 | #[borsh(crate = "calimero_sdk::borsh")] 10 | pub struct GenExt; 11 | 12 | #[app::logic] 13 | impl GenExt { 14 | #[app::init] 15 | pub fn init() -> GenExt { 16 | GenExt 17 | } 18 | 19 | pub fn view_account(&mut self, account_id: &str, block_height: u64) -> String { 20 | let client = Client::testnet(); 21 | let request = RpcQueryRequest { 22 | block_id: calimero_sdk_near::BlockId::Height(block_height), 23 | request: QueryRequest::ViewAccount { 24 | account_id: account_id.parse().unwrap(), 25 | }, 26 | }; 27 | match client.call(request) { 28 | Ok(r) => format!("{:?}", r), 29 | Err(e) => format!("{:?}", e), 30 | } 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /apps/kv-store/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "kv-store" 3 | version = "0.1.0" 4 | authors.workspace = true 5 | edition.workspace = true 6 | repository.workspace = true 7 | license.workspace = true 8 | 9 | [lib] 10 | crate-type = ["cdylib"] 11 | 12 | [dependencies] 13 | thiserror.workspace = true 14 | calimero-sdk.workspace = true 15 | calimero-storage.workspace = true 16 | -------------------------------------------------------------------------------- /apps/kv-store/build.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -e 3 | 4 | cd "$(dirname $0)" 5 | 6 | TARGET="${CARGO_TARGET_DIR:-../../target}" 7 | 8 | rustup target add wasm32-unknown-unknown 9 | 10 | cargo build --target wasm32-unknown-unknown --profile app-release 11 | 12 | mkdir -p res 13 | 14 | cp $TARGET/wasm32-unknown-unknown/app-release/kv_store.wasm ./res/ 15 | 16 | if command -v wasm-opt > /dev/null; then 17 | wasm-opt -Oz ./res/kv_store.wasm -o ./res/kv_store.wasm 18 | fi 19 | -------------------------------------------------------------------------------- /apps/only-peers/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "only-peers" 3 | version = "0.1.0" 4 | authors.workspace = true 5 | edition.workspace = true 6 | repository.workspace = true 7 | license.workspace = true 8 | 9 | [lib] 10 | crate-type = ["cdylib"] 11 | 12 | [dependencies] 13 | calimero-sdk.workspace = true 14 | calimero-storage.workspace = true 15 | -------------------------------------------------------------------------------- /apps/only-peers/build.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -e 3 | 4 | cd "$(dirname $0)" 5 | 6 | TARGET="${CARGO_TARGET_DIR:-../../target}" 7 | 8 | rustup target add wasm32-unknown-unknown 9 | 10 | cargo build --target wasm32-unknown-unknown --profile app-release 11 | 12 | mkdir -p res 13 | 14 | cp $TARGET/wasm32-unknown-unknown/app-release/only_peers.wasm ./res/ 15 | 16 | if command -v wasm-opt > /dev/null; then 17 | wasm-opt -Oz ./res/only_peers.wasm -o ./res/only_peers.wasm 18 | fi 19 | -------------------------------------------------------------------------------- /apps/visited/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "visited" 3 | version = "0.1.0" 4 | authors.workspace = true 5 | edition.workspace = true 6 | repository.workspace = true 7 | license.workspace = true 8 | 9 | [lib] 10 | crate-type = ["cdylib"] 11 | 12 | [dependencies] 13 | calimero-sdk.workspace = true 14 | calimero-storage.workspace = true 15 | -------------------------------------------------------------------------------- /apps/visited/build.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -e 3 | 4 | cd "$(dirname $0)" 5 | 6 | TARGET="${CARGO_TARGET_DIR:-../../target}" 7 | 8 | rustup target add wasm32-unknown-unknown 9 | 10 | cargo build --target wasm32-unknown-unknown --profile app-release 11 | 12 | mkdir -p res 13 | 14 | cp $TARGET/wasm32-unknown-unknown/app-release/visited.wasm ./res/ 15 | 16 | if command -v wasm-opt > /dev/null; then 17 | wasm-opt -Oz ./res/visited.wasm -o ./res/visited.wasm 18 | fi 19 | -------------------------------------------------------------------------------- /apps/visited/src/lib.rs: -------------------------------------------------------------------------------- 1 | #![allow(clippy::len_without_is_empty)] 2 | 3 | use std::result::Result; 4 | 5 | use calimero_sdk::app; 6 | use calimero_sdk::borsh::{BorshDeserialize, BorshSerialize}; 7 | use calimero_sdk::types::Error; 8 | use calimero_storage::collections::{UnorderedMap, UnorderedSet}; 9 | 10 | #[app::state] 11 | #[derive(Debug, BorshSerialize, BorshDeserialize)] 12 | #[borsh(crate = "calimero_sdk::borsh")] 13 | pub struct VisitedCities { 14 | visited: UnorderedMap>, 15 | } 16 | 17 | #[app::logic] 18 | impl VisitedCities { 19 | #[app::init] 20 | pub fn init() -> VisitedCities { 21 | VisitedCities { 22 | visited: UnorderedMap::new(), 23 | } 24 | } 25 | 26 | pub fn add_person(&mut self, person: String) -> Result { 27 | Ok(self.visited.insert(person, UnorderedSet::new())?.is_some()) 28 | } 29 | 30 | pub fn add_visited_city(&mut self, person: String, city: String) -> Result { 31 | Ok(self.visited.get(&person)?.unwrap().insert(city)?) 32 | } 33 | 34 | pub fn get_person_with_most_cities_visited(&self) -> Result { 35 | let mut max = 0; 36 | let mut person = String::new(); 37 | 38 | for entry in self.visited.entries()? { 39 | let (person_key, cities_set) = entry; 40 | if cities_set.len()? > max { 41 | max = cities_set.len()?; 42 | person = person_key.clone(); 43 | } 44 | } 45 | Ok(person) 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /contracts/icp/.gitignore: -------------------------------------------------------------------------------- 1 | # Various IDEs and Editors 2 | .vscode/ 3 | .idea/ 4 | **/*~ 5 | 6 | # Mac OSX temporary files 7 | .DS_Store 8 | **/.DS_Store 9 | 10 | # dfx temporary files 11 | .dfx/ 12 | 13 | # generated files 14 | **/declarations/ 15 | 16 | # rust 17 | target/ 18 | 19 | # frontend code 20 | node_modules/ 21 | dist/ 22 | .svelte-kit/ 23 | 24 | # environment variables 25 | .env 26 | 27 | # testing library 28 | pocket-ic 29 | 30 | !**/res/*.did 31 | **/canister_ids.json 32 | -------------------------------------------------------------------------------- /contracts/icp/context-config/.env: -------------------------------------------------------------------------------- 1 | 2 | # DFX CANISTER ENVIRONMENT VARIABLES 3 | DFX_VERSION='0.24.3' 4 | DFX_NETWORK='local' 5 | CANISTER_ID_LEDGER='bd3sg-teaaa-aaaaa-qaaba-cai' 6 | CANISTER_ID_CONTEXT_CONTRACT='bkyz2-fmaaa-aaaaa-qaaaq-cai' 7 | CANISTER_ID='bkyz2-fmaaa-aaaaa-qaaaq-cai' 8 | CANISTER_CANDID_PATH='/Users/alen/www/calimero/core/contracts/icp/context-config/./res/calimero_context_config_icp.did' 9 | # END DFX CANISTER ENVIRONMENT VARIABLES -------------------------------------------------------------------------------- /contracts/icp/context-config/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "calimero-context-config-icp" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 7 | 8 | [lib] 9 | crate-type = ["cdylib", "rlib"] 10 | 11 | [dependencies] 12 | bs58.workspace = true 13 | calimero-context-config = { workspace = true, features = ["icp"] } 14 | candid.workspace = true 15 | ed25519-dalek.workspace = true 16 | ic-cdk = "0.16" 17 | ic-cdk-macros = "0.16" 18 | serde = { version = "1.0", features = ["derive"] } 19 | serde_json.workspace = true 20 | thiserror.workspace = true 21 | 22 | [dev-dependencies] 23 | pocket-ic = "6.0.0" 24 | rand = "0.8" 25 | ed25519-dalek = "2.0" 26 | -------------------------------------------------------------------------------- /contracts/icp/context-config/build.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | set -e 3 | 4 | cd "$(dirname $0)" 5 | 6 | TARGET="${CARGO_TARGET_DIR:-../../../target}" 7 | 8 | rustup target add wasm32-unknown-unknown 9 | 10 | cargo build --target wasm32-unknown-unknown --profile app-release 11 | 12 | mkdir -p res 13 | 14 | cp $TARGET/wasm32-unknown-unknown/app-release/calimero_context_config_icp.wasm ./res/ 15 | 16 | if command -v wasm-opt > /dev/null; then 17 | wasm-opt -Oz ./res/calimero_context_config_icp.wasm -o ./res/calimero_context_config_icp.wasm 18 | fi 19 | 20 | if command -v candid-extractor > /dev/null; then 21 | candid-extractor ./res/calimero_context_config_icp.wasm > ./res/calimero_context_config_icp.did 22 | fi 23 | -------------------------------------------------------------------------------- /contracts/icp/context-config/dfx.json: -------------------------------------------------------------------------------- 1 | { 2 | "canisters": { 3 | "context_contract": { 4 | "package": "calimero-context-config-icp", 5 | "candid": "./res/calimero_context_config_icp.did", 6 | "type": "rust" 7 | }, 8 | "ledger": { 9 | "type": "custom", 10 | "wasm": "https://download.dfinity.systems/ic/aba60ffbc46acfc8990bf4d5685c1360bd7026b9/canisters/ledger-canister.wasm.gz", 11 | "candid": "https://raw.githubusercontent.com/dfinity/ic/aba60ffbc46acfc8990bf4d5685c1360bd7026b9/rs/ledger_suite/icp/ledger.did" 12 | } 13 | }, 14 | "defaults": { 15 | "build": { 16 | "args": "", 17 | "packtool": "" 18 | } 19 | }, 20 | "networks": { 21 | "local": { 22 | "bind": "127.0.0.1:4943", 23 | "type": "persistent" 24 | } 25 | }, 26 | "routing_table": { 27 | "start_canister_id": "aaaaa-aa", 28 | "end_canister_id": "zzzzz-zz" 29 | }, 30 | "version": 1 31 | } 32 | -------------------------------------------------------------------------------- /contracts/icp/context-config/res/calimero_context_config_icp.did: -------------------------------------------------------------------------------- 1 | type ICApplication = record { 2 | id : blob; 3 | source : text; 4 | metadata : blob; 5 | "blob" : blob; 6 | size : nat64; 7 | }; 8 | type ICCapability = variant { Proxy; ManageMembers; ManageApplication }; 9 | type ICSigned = record { signature : blob; _phantom : null; payload : blob }; 10 | type Result = variant { Ok; Err : text }; 11 | service : (principal) -> { 12 | application : (blob) -> (ICApplication) query; 13 | application_revision : (blob) -> (nat64) query; 14 | fetch_nonce : (blob, blob) -> (opt nat64) query; 15 | has_member : (blob, blob) -> (bool) query; 16 | members : (blob, nat64, nat64) -> (vec blob) query; 17 | members_revision : (blob) -> (nat64) query; 18 | mutate : (ICSigned) -> (Result); 19 | privileges : (blob, vec blob) -> ( 20 | vec record { blob; vec ICCapability }, 21 | ) query; 22 | proxy_contract : (blob) -> (principal) query; 23 | set_proxy_code : (blob) -> (Result); 24 | } 25 | -------------------------------------------------------------------------------- /contracts/icp/context-proxy/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "calimero-context-proxy-icp" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | [lib] 7 | crate-type = ["cdylib", "rlib"] 8 | 9 | [dependencies] 10 | bs58.workspace = true 11 | calimero-context-config = { workspace = true, features = ["icp"] } 12 | candid = { version = "0.10", features = ["value"] } 13 | ed25519-dalek.workspace = true 14 | hex.workspace = true 15 | ic-cdk = "0.16" 16 | ic-cdk-macros = "0.16" 17 | ic-ledger-types = "0.14.0" 18 | serde = { version = "1.0", features = ["derive"] } 19 | thiserror.workspace = true 20 | 21 | [dev-dependencies] 22 | pocket-ic = "6.0.0" 23 | rand = "0.8" 24 | reqwest = { version = "0.10.10", features = ["blocking"] } 25 | flate2 = "1.0.35" 26 | -------------------------------------------------------------------------------- /contracts/icp/context-proxy/build.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | set -e 3 | 4 | cd "$(dirname $0)" 5 | 6 | TARGET="${CARGO_TARGET_DIR:-../../../target}" 7 | 8 | rustup target add wasm32-unknown-unknown 9 | 10 | cargo build --target wasm32-unknown-unknown --profile app-release 11 | 12 | mkdir -p res 13 | 14 | cp $TARGET/wasm32-unknown-unknown/app-release/calimero_context_proxy_icp.wasm ./res/ 15 | 16 | if command -v wasm-opt > /dev/null; then 17 | wasm-opt -Oz ./res/calimero_context_proxy_icp.wasm -o ./res/calimero_context_proxy_icp.wasm 18 | fi 19 | 20 | if command -v candid-extractor > /dev/null; then 21 | candid-extractor ./res/calimero_context_proxy_icp.wasm > ./res/calimero_context_proxy_icp.did 22 | fi 23 | -------------------------------------------------------------------------------- /contracts/icp/context-proxy/build_contracts.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | set -e 3 | 4 | cd "$(dirname $0)" 5 | 6 | echo "Building proxy contract..." 7 | ./build.sh 8 | 9 | echo "Building mock external contract..." 10 | ./mock/external/build.sh 11 | 12 | echo "Building context-config contract..." 13 | ../context-config/build.sh 14 | -------------------------------------------------------------------------------- /contracts/icp/context-proxy/dfx.json: -------------------------------------------------------------------------------- 1 | { 2 | "canisters": { 3 | "proxy_contract": { 4 | "package": "calimero-context-proxy-icp", 5 | "candid": "./res/calimero_context_proxy_icp.did", 6 | "type": "rust" 7 | }, 8 | "mock_external": { 9 | "type": "rust", 10 | "package": "calimero-mock-external-icp", 11 | "candid": "./mock/external/res/calimero_mock_external_icp.did", 12 | "path": "mock/external" 13 | } 14 | }, 15 | "defaults": { 16 | "build": { 17 | "args": "", 18 | "packtool": "" 19 | } 20 | }, 21 | "networks": { 22 | "local": { 23 | "bind": "127.0.0.1:4943", 24 | "type": "persistent" 25 | } 26 | }, 27 | "output_env_file": ".env", 28 | "version": 1 29 | } 30 | -------------------------------------------------------------------------------- /contracts/icp/context-proxy/mock/external/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "calimero-mock-external-icp" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | [lib] 7 | crate-type = ["cdylib"] 8 | 9 | [dependencies] 10 | candid = "0.10" 11 | ic-cdk = "0.16" 12 | ic-cdk-macros = "0.16" 13 | ic-ledger-types = "0.14.0" 14 | -------------------------------------------------------------------------------- /contracts/icp/context-proxy/mock/external/build.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | set -e 3 | 4 | cd "$(dirname $0)" 5 | 6 | TARGET="${CARGO_TARGET_DIR:-../../../../../target}" 7 | 8 | rustup target add wasm32-unknown-unknown 9 | 10 | cargo build --target wasm32-unknown-unknown --profile app-release 11 | 12 | mkdir -p res 13 | 14 | cp $TARGET/wasm32-unknown-unknown/app-release/calimero_mock_external_icp.wasm ./res/ 15 | 16 | if command -v wasm-opt > /dev/null; then 17 | wasm-opt -Oz ./res/calimero_mock_external_icp.wasm -o ./res/calimero_mock_external_icp.wasm 18 | fi 19 | 20 | if command -v candid-extractor > /dev/null; then 21 | candid-extractor ./res/calimero_mock_external_icp.wasm > ./res/calimero_mock_external_icp.did 22 | fi 23 | -------------------------------------------------------------------------------- /contracts/icp/context-proxy/mock/external/res/calimero_mock_external_icp.did: -------------------------------------------------------------------------------- 1 | service : (principal) -> { 2 | clear_state : () -> (); 3 | get_calls : () -> (vec blob) query; 4 | test_method : (blob) -> (blob); 5 | test_method_no_transfer : (blob) -> (blob); 6 | } 7 | -------------------------------------------------------------------------------- /contracts/icp/context-proxy/src/sys.rs: -------------------------------------------------------------------------------- 1 | use candid::{CandidType, Deserialize}; 2 | 3 | use crate::{ICProxyContract, PROXY_CONTRACT}; 4 | 5 | #[derive(CandidType, Deserialize)] 6 | struct StableStorage { 7 | saved_state: ICProxyContract, 8 | } 9 | 10 | #[ic_cdk::pre_upgrade] 11 | fn pre_upgrade() { 12 | let state = PROXY_CONTRACT.with(|state| { 13 | let state = state 14 | .borrow_mut() 15 | .take() 16 | .expect("cannister is being upgraded"); 17 | 18 | if ic_cdk::caller() != state.context_config_id { 19 | ic_cdk::trap("unauthorized: only context contract can upgrade proxy"); 20 | } 21 | 22 | StableStorage { saved_state: state } 23 | }); 24 | 25 | // Write state to stable storage 26 | match ic_cdk::storage::stable_save((state,)) { 27 | Ok(_) => (), 28 | Err(err) => ic_cdk::trap(&format!("Failed to save stable storage: {}", err)), 29 | } 30 | } 31 | 32 | #[ic_cdk::post_upgrade] 33 | fn post_upgrade() { 34 | // Restore the contract state 35 | match ic_cdk::storage::stable_restore::<(StableStorage,)>() { 36 | Ok((StableStorage { saved_state },)) => { 37 | PROXY_CONTRACT.with(|state| { 38 | let mut state = state.borrow_mut(); 39 | 40 | if state.is_some() { 41 | ic_cdk::trap("cannister state already exists??"); 42 | } 43 | 44 | *state = Some(saved_state); 45 | }); 46 | } 47 | Err(err) => ic_cdk::trap(&format!("Failed to restore stable storage: {}", err)), 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /contracts/near/context-config/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "calimero-context-config-near" 3 | version = "0.1.0" 4 | authors.workspace = true 5 | edition.workspace = true 6 | repository.workspace = true 7 | license.workspace = true 8 | 9 | [lib] 10 | crate-type = ["rlib", "cdylib"] 11 | 12 | [dependencies] 13 | cfg-if.workspace = true 14 | near-sdk = { workspace = true, features = ["unstable"] } 15 | calimero-context-config.workspace = true 16 | 17 | [dev-dependencies] 18 | ed25519-dalek.workspace = true 19 | eyre.workspace = true 20 | near-crypto.workspace = true 21 | near-workspaces.workspace = true 22 | rand.workspace = true 23 | serde_json.workspace = true 24 | tokio.workspace = true 25 | 26 | [lints] 27 | workspace = true 28 | 29 | [features] 30 | default = [] 31 | 32 | migrations = [] 33 | ## migrations (mutually exclusive) ## 34 | 01_guard_revisions = [] 35 | 02_nonces = [] 36 | ## migrations (mutually exclusive) ## 37 | -------------------------------------------------------------------------------- /contracts/near/context-config/build.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | set -e 3 | 4 | cd "$(dirname $0)" 5 | 6 | TARGET="${CARGO_TARGET_DIR:-../../../target}" 7 | 8 | rustup target add wasm32-unknown-unknown 9 | 10 | if [ "$1" = "--migration" ]; then 11 | selected_migration=$2 12 | extra_args="--features migrations,$selected_migration" 13 | fi 14 | 15 | cargo build --target wasm32-unknown-unknown --profile app-release $extra_args 16 | 17 | mkdir -p res 18 | 19 | cp $TARGET/wasm32-unknown-unknown/app-release/calimero_context_config_near.wasm ./res/ 20 | 21 | if command -v wasm-opt > /dev/null; then 22 | wasm-opt -Oz ./res/calimero_context_config_near.wasm -o ./res/calimero_context_config_near.wasm 23 | fi 24 | -------------------------------------------------------------------------------- /contracts/near/context-config/src/sys/migrations.rs: -------------------------------------------------------------------------------- 1 | use cfg_if::cfg_if; 2 | 3 | #[cfg(feature = "migrations")] 4 | const _: () = { 5 | use near_sdk::near; 6 | 7 | use crate::{ContextConfigs, ContextConfigsExt}; 8 | 9 | #[near] 10 | impl ContextConfigs { 11 | #[private] 12 | pub fn migrate() { 13 | directive::migrate(); 14 | } 15 | } 16 | }; 17 | 18 | migrations! { 19 | "01_guard_revisions" => "migrations/01_guard_revisions.rs", 20 | "02_nonces" => "migrations/02_nonces.rs", 21 | } 22 | 23 | // --- 24 | 25 | macro_rules! _migrations { 26 | ($($migration:literal => $path:literal,)+) => { 27 | $( 28 | #[cfg(feature = $migration)] 29 | #[path = $path] 30 | mod directive; /* migrations are exclusive */ 31 | 32 | #[cfg(all(feature = $migration, not(feature = "migrations")))] 33 | compile_error!("migration selected without migrations enabled"); 34 | )+ 35 | 36 | cfg_if! { 37 | if #[cfg(not(feature = "migrations"))] {} 38 | $( 39 | else if #[cfg(feature = $migration)] {} 40 | )+ 41 | else { 42 | mod directive { 43 | pub fn migrate() { 44 | /* no op */ 45 | } 46 | } 47 | 48 | compile_error!("migrations enabled, but no migration selected"); 49 | } 50 | } 51 | }; 52 | } 53 | 54 | use _migrations as migrations; 55 | -------------------------------------------------------------------------------- /contracts/near/context-config/src/sys/migrations/01_guard_revisions.rs: -------------------------------------------------------------------------------- 1 | use std::io; 2 | 3 | use calimero_context_config::repr::Repr; 4 | use calimero_context_config::types::{Application, ContextId, ContextIdentity, SignerId}; 5 | use near_sdk::store::{IterableMap, IterableSet}; 6 | use near_sdk::{env, near}; 7 | 8 | use crate::Config; 9 | 10 | #[derive(Debug)] 11 | #[near(serializers = [borsh])] 12 | pub struct OldContextConfigs { 13 | contexts: IterableMap, 14 | config: Config, 15 | } 16 | 17 | #[derive(Debug)] 18 | #[near(serializers = [borsh])] 19 | struct OldContext { 20 | pub application: OldGuard>, 21 | pub members: OldGuard>, 22 | } 23 | 24 | #[derive(Debug)] 25 | #[near(serializers = [borsh])] 26 | pub struct OldGuard { 27 | inner: T, 28 | #[borsh(deserialize_with = "skipped")] 29 | revision: u64, 30 | priviledged: IterableSet, 31 | } 32 | 33 | #[expect(clippy::unnecessary_wraps, reason = "borsh needs this")] 34 | pub fn skipped(_reader: &mut R) -> Result { 35 | Ok(Default::default()) 36 | } 37 | 38 | pub fn migrate() { 39 | // IterableMap doesn't support raw access to the underlying storage 40 | // Which hinders migration of the data, so we have to employ this trick 41 | 42 | let mut state = env::state_read::().expect("failed to read state"); 43 | 44 | for (context_id, _) in state.contexts.iter_mut() { 45 | env::log_str(&format!("Migrating context `{}`", Repr::new(*context_id))); 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /contracts/near/context-proxy/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "calimero-context-proxy-near" 3 | version = "0.1.0" 4 | authors.workspace = true 5 | edition.workspace = true 6 | repository.workspace = true 7 | license.workspace = true 8 | 9 | [lib] 10 | crate-type = ["rlib", "cdylib"] 11 | 12 | [dependencies] 13 | near-sdk.workspace = true 14 | calimero-context-config.workspace = true 15 | 16 | [dev-dependencies] 17 | tokio.workspace = true 18 | serde = { workspace = true, features = ["derive"] } 19 | serde_json.workspace = true 20 | eyre.workspace = true 21 | near-workspaces.workspace = true 22 | near-crypto.workspace = true 23 | calimero-context-config-near.workspace = true 24 | ed25519-dalek.workspace = true 25 | rand.workspace = true 26 | 27 | [features] 28 | __internal_explode_size = [] 29 | -------------------------------------------------------------------------------- /contracts/near/context-proxy/build-test-deps.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | set -e 3 | 4 | cd "$(dirname $0)" 5 | 6 | CONTEXT_PROXY_CONTRACT_PATH="./" 7 | CONTEXT_CONFIG_CONTRACT_PATH="../context-config" 8 | TEST_CONTRACT_PATH="../test-counter" 9 | 10 | echo "Building context-proxy contract..." 11 | $CONTEXT_PROXY_CONTRACT_PATH/build.sh --test 12 | 13 | echo "Building context-config contract..." 14 | $CONTEXT_CONFIG_CONTRACT_PATH/build.sh 15 | 16 | echo "Building test-counter contract..." 17 | $TEST_CONTRACT_PATH/build.sh 18 | -------------------------------------------------------------------------------- /contracts/near/context-proxy/build.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | set -e 3 | 4 | cd "$(dirname $0)" 5 | 6 | TARGET="${CARGO_TARGET_DIR:-../../../target}" 7 | 8 | rustup target add wasm32-unknown-unknown 9 | 10 | mkdir -p res 11 | 12 | if [ "$1" = "--test" ]; then 13 | cargo build --target wasm32-unknown-unknown --profile app-release --features __internal_explode_size 14 | 15 | cp $TARGET/wasm32-unknown-unknown/app-release/calimero_context_proxy_near.wasm ./res/calimero_context_proxy_near_fat.wasm 16 | fi 17 | 18 | cargo build --target wasm32-unknown-unknown --profile app-release 19 | 20 | cp $TARGET/wasm32-unknown-unknown/app-release/calimero_context_proxy_near.wasm ./res/ 21 | 22 | if command -v wasm-opt > /dev/null; then 23 | wasm-opt -Oz ./res/calimero_context_proxy_near.wasm -o ./res/calimero_context_proxy_near.wasm 24 | fi 25 | -------------------------------------------------------------------------------- /contracts/near/context-proxy/src/ext_config.rs: -------------------------------------------------------------------------------- 1 | use calimero_context_config::repr::Repr; 2 | use calimero_context_config::types::{ContextId, SignerId}; 3 | use near_sdk::ext_contract; 4 | 5 | #[ext_contract(config_contract)] 6 | pub trait ConfigContract { 7 | fn has_member(&self, context_id: Repr, identity: Repr) -> bool; 8 | } 9 | -------------------------------------------------------------------------------- /contracts/near/context-proxy/test.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | set -e 3 | 4 | ./build-test-deps.sh 5 | 6 | echo "Running tests..." 7 | cargo test -- --nocapture 8 | -------------------------------------------------------------------------------- /contracts/near/context-proxy/tests/common/counter_helper.rs: -------------------------------------------------------------------------------- 1 | use eyre::Result; 2 | use near_workspaces::network::Sandbox; 3 | use near_workspaces::{Contract, Worker}; 4 | 5 | use super::deploy_contract; 6 | 7 | const COUNTER_WASM: &str = "../test-counter/res/calimero_test_counter_near.wasm"; 8 | 9 | #[derive(Clone)] 10 | pub struct CounterContractHelper { 11 | pub counter_contract: Contract, 12 | } 13 | 14 | impl CounterContractHelper { 15 | pub async fn deploy_and_initialize(worker: &Worker) -> Result { 16 | let counter_contract = deploy_contract(worker, COUNTER_WASM).await?; 17 | 18 | let _res = counter_contract 19 | .call("new") 20 | .transact() 21 | .await? 22 | .into_result()?; 23 | Ok(Self { counter_contract }) 24 | } 25 | 26 | pub async fn get_value(&self) -> Result { 27 | let counter_value: u32 = self.counter_contract.view("get_count").await?.json()?; 28 | Ok(counter_value) 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /contracts/near/context-proxy/tests/common/mod.rs: -------------------------------------------------------------------------------- 1 | use ed25519_dalek::SigningKey; 2 | use eyre::Result; 3 | use near_workspaces::network::Sandbox; 4 | use near_workspaces::types::NearToken; 5 | use near_workspaces::{Account, Contract, Worker}; 6 | use rand::Rng; 7 | 8 | pub mod config_helper; 9 | pub mod counter_helper; 10 | pub mod proxy_lib_helper; 11 | 12 | pub async fn deploy_contract(worker: &Worker, wasm_path: &str) -> Result { 13 | let wasm = std::fs::read(wasm_path)?; 14 | let contract = worker.dev_deploy(&wasm).await?; 15 | Ok(contract) 16 | } 17 | 18 | pub fn generate_keypair() -> Result { 19 | let mut rng = rand::thread_rng(); 20 | let sk = SigningKey::from_bytes(&rng.gen()); 21 | Ok(sk) 22 | } 23 | 24 | pub async fn create_account_with_balance( 25 | worker: &Worker, 26 | prefix: &str, 27 | balance: u128, 28 | ) -> Result { 29 | let random_suffix: u32 = rand::thread_rng().gen_range(0..999999); 30 | 31 | // Take first 8 chars of prefix and combine with random number 32 | let prefix = prefix.chars().take(8).collect::(); 33 | let account_id = format!("{}{}", prefix, random_suffix); 34 | 35 | let root_account = worker.root_account()?; 36 | let account = root_account 37 | .create_subaccount(&account_id) 38 | .initial_balance(NearToken::from_near(balance)) 39 | .transact() 40 | .await? 41 | .into_result()?; 42 | Ok(account) 43 | } 44 | -------------------------------------------------------------------------------- /contracts/near/registry/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "calimero-registry" 3 | version = "0.1.0" 4 | authors.workspace = true 5 | edition.workspace = true 6 | repository.workspace = true 7 | license.workspace = true 8 | 9 | [lib] 10 | crate-type = ["rlib", "cdylib"] 11 | 12 | [dependencies] 13 | hex.workspace = true 14 | near-sdk.workspace = true 15 | semver.workspace = true 16 | 17 | [dev-dependencies] 18 | near-sdk = { workspace = true, features = ["unit-testing"] } 19 | near-workspaces.workspace = true 20 | serde_json.workspace = true 21 | tokio.workspace = true 22 | 23 | [lints] 24 | workspace = true 25 | -------------------------------------------------------------------------------- /contracts/near/registry/README.md: -------------------------------------------------------------------------------- 1 | # Registry 2 | 3 | Calimero package registry implementation with Near Protocol. 4 | 5 | ## Management 6 | 7 | Contract build and deployment is manual process. The following steps are 8 | required to build and deploy the contract. 9 | 10 | 1. `./build.sh` - build the contract 11 | 2. `./deploy.sh` - deploy the contract 12 | 13 | Contract is currently deployed on `testnet` under 14 | `calimero-package-manager.testnet` account. 15 | -------------------------------------------------------------------------------- /contracts/near/registry/build.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | set -e 3 | 4 | cd "$(dirname $0)" 5 | 6 | TARGET="${CARGO_TARGET_DIR:-../../../target}" 7 | 8 | rustup target add wasm32-unknown-unknown 9 | 10 | cargo build --target wasm32-unknown-unknown --profile app-release 11 | 12 | mkdir -p res 13 | 14 | cp $TARGET/wasm32-unknown-unknown/app-release/calimero_registry.wasm ./res/ 15 | 16 | if command -v wasm-opt > /dev/null; then 17 | wasm-opt -Oz ./res/calimero_registry.wasm -o ./res/calimero_registry.wasm 18 | fi 19 | -------------------------------------------------------------------------------- /contracts/near/registry/deploy.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | set -e 3 | 4 | cd "$(dirname $0)" 5 | 6 | near contract deploy \ 7 | calimero-package-manager.testnet \ 8 | use-file ./res/calimero_registry.wasm \ 9 | without-init-call \ 10 | network-config testnet \ 11 | sign-with-legacy-keychain \ 12 | send 13 | -------------------------------------------------------------------------------- /contracts/near/test-counter/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "calimero-test-counter-near" 3 | version = "0.1.0" 4 | authors.workspace = true 5 | edition.workspace = true 6 | repository.workspace = true 7 | license.workspace = true 8 | 9 | [lib] 10 | crate-type = ["rlib", "cdylib"] 11 | 12 | [dependencies] 13 | near-sdk.workspace = true 14 | 15 | [dev-dependencies] 16 | eyre.workspace = true 17 | near-workspaces.workspace = true 18 | tokio.workspace = true 19 | 20 | [lints] 21 | workspace = true 22 | -------------------------------------------------------------------------------- /contracts/near/test-counter/build.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | set -e 3 | 4 | cd "$(dirname $0)" 5 | 6 | TARGET="${CARGO_TARGET_DIR:-../../../target}" 7 | 8 | rustup target add wasm32-unknown-unknown 9 | 10 | cargo build --target wasm32-unknown-unknown --profile app-release 11 | 12 | mkdir -p res 13 | 14 | cp $TARGET/wasm32-unknown-unknown/app-release/calimero_test_counter_near.wasm ./res/ 15 | 16 | if command -v wasm-opt > /dev/null; then 17 | wasm-opt -Oz ./res/calimero_test_counter_near.wasm -o ./res/calimero_test_counter_near.wasm 18 | fi 19 | -------------------------------------------------------------------------------- /contracts/near/test-counter/src/lib.rs: -------------------------------------------------------------------------------- 1 | #![allow( 2 | clippy::use_self, 3 | clippy::must_use_candidate, 4 | unused_crate_dependencies, 5 | reason = "False positives" 6 | )] 7 | 8 | use near_sdk::{env, near, PanicOnDefault}; 9 | 10 | #[near(contract_state)] 11 | #[derive(PanicOnDefault, Clone, Copy, Debug)] 12 | pub struct CounterContract { 13 | counter: u32, 14 | } 15 | 16 | #[near] 17 | impl CounterContract { 18 | #[init] 19 | pub const fn new() -> Self { 20 | Self { counter: 0 } 21 | } 22 | 23 | pub fn increment(&mut self) { 24 | self.counter = self.counter.wrapping_add(1); 25 | env::log_str("Counter incremented"); 26 | } 27 | 28 | pub const fn get_count(&self) -> u32 { 29 | self.counter 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /contracts/near/test-counter/test.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | set -e 3 | 4 | CONTRACT_PATH="./" 5 | 6 | echo "Building context-config contract..." 7 | (cd $CONTRACT_PATH && ./build.sh) 8 | 9 | echo "Running tests..." 10 | cargo test -- --nocapture 11 | -------------------------------------------------------------------------------- /contracts/near/test-counter/tests/sandbox.rs: -------------------------------------------------------------------------------- 1 | #![allow(unused_crate_dependencies, reason = "False positives")] 2 | 3 | use near_workspaces::network::Sandbox; 4 | use near_workspaces::{Contract, Worker}; 5 | 6 | const CONTRACT_WASM: &str = "./res/calimero_test_counter_near.wasm"; 7 | 8 | async fn deploy_counter_contract(worker: &Worker) -> eyre::Result { 9 | let wasm = std::fs::read(CONTRACT_WASM)?; 10 | let contract = worker.dev_deploy(&wasm).await?; 11 | Ok(contract) 12 | } 13 | 14 | #[tokio::test] 15 | async fn test_counter_contract() -> eyre::Result<()> { 16 | let worker = near_workspaces::sandbox().await?; 17 | let counter_contract = deploy_counter_contract(&worker).await?; 18 | 19 | let _res = counter_contract 20 | .call("new") 21 | .transact() 22 | .await? 23 | .into_result()?; 24 | 25 | let _res = counter_contract 26 | .call("increment") 27 | .transact() 28 | .await? 29 | .into_result()?; 30 | 31 | let counter_value: u32 = counter_contract.view("get_count").await?.json()?; 32 | 33 | assert_eq!(counter_value, 1, "Counter should be incremented once"); 34 | 35 | Ok(()) 36 | } 37 | -------------------------------------------------------------------------------- /contracts/stellar/context-config/.gitignore: -------------------------------------------------------------------------------- 1 | # Rust's output directory 2 | target 3 | 4 | # Local settings 5 | .soroban 6 | .stellar 7 | 8 | # Test folder 9 | test_snapshots/ 10 | mock_proxy/test_snapshots/ 11 | 12 | # Res folder 13 | res/ -------------------------------------------------------------------------------- /contracts/stellar/context-config/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "calimero-context-config-stellar" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | [lib] 7 | crate-type = ["cdylib", "rlib"] 8 | 9 | [dependencies] 10 | ed25519-dalek = { version = "2.0.0", default-features = false, features = ["hazmat"] } 11 | soroban-sdk = { workspace = true, features = ["alloc"] } 12 | soroban-env-common = { workspace = true } 13 | stellar-xdr = { workspace = true, default-features = false } 14 | 15 | calimero-context-config = { workspace = true, features = ["stellar"] } 16 | 17 | [dev-dependencies] 18 | soroban-sdk = { workspace = true, features = ["testutils"] } 19 | 20 | [features] 21 | testutils = ["soroban-sdk/testutils"] -------------------------------------------------------------------------------- /contracts/stellar/context-config/Makefile: -------------------------------------------------------------------------------- 1 | default: build 2 | 3 | all: test 4 | 5 | test: build 6 | cargo test 7 | 8 | build: 9 | stellar contract build 10 | @ls -l target/wasm32-unknown-unknown/release/*.wasm 11 | 12 | fmt: 13 | cargo fmt --all 14 | 15 | clean: 16 | cargo clean 17 | -------------------------------------------------------------------------------- /contracts/stellar/context-config/build.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | set -e 3 | 4 | cd "$(dirname $0)" 5 | 6 | TARGET="${CARGO_TARGET_DIR:-../../../target}" 7 | 8 | rustup target add wasm32-unknown-unknown 9 | 10 | cargo build --target wasm32-unknown-unknown --profile app-release 11 | 12 | mkdir -p res 13 | 14 | cp $TARGET/wasm32-unknown-unknown/app-release/calimero_context_config_stellar.wasm ./res/ 15 | 16 | if command -v wasm-opt > /dev/null; then 17 | wasm-opt -Oz ./res/calimero_context_config_stellar.wasm -o ./res/calimero_context_config_stellar.wasm 18 | fi 19 | -------------------------------------------------------------------------------- /contracts/stellar/context-proxy/.gitignore: -------------------------------------------------------------------------------- 1 | # Build artifacts 2 | target/ 3 | res/ 4 | *.wasm 5 | 6 | # Test artifacts 7 | test_snapshots/ 8 | test_snapshot/ 9 | 10 | # IDE files 11 | .idea/ 12 | .vscode/ 13 | *.iml 14 | 15 | # OS files 16 | .DS_Store 17 | Thumbs.db 18 | 19 | # Rust 20 | **/*.rs.bk 21 | Cargo.lock -------------------------------------------------------------------------------- /contracts/stellar/context-proxy/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "calimero-context-proxy-stellar" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | [lib] 7 | crate-type = ["cdylib", "rlib"] 8 | 9 | [dependencies] 10 | ed25519-dalek = { version = "2.0.0", default-features = false, features = ["hazmat"] } 11 | soroban-sdk = { workspace = true, features = ["alloc"] } 12 | soroban-env-common = { workspace = true } 13 | stellar-xdr = { workspace = true, default-features = false } 14 | 15 | calimero-context-config = { workspace = true, features = ["stellar"] } 16 | 17 | [dev-dependencies] 18 | soroban-sdk = { workspace = true, features = ["testutils"] } 19 | 20 | [features] 21 | testutils = ["soroban-sdk/testutils"] -------------------------------------------------------------------------------- /contracts/stellar/context-proxy/Makefile: -------------------------------------------------------------------------------- 1 | default: build 2 | 3 | all: test 4 | 5 | test: build 6 | cargo test 7 | 8 | build: 9 | stellar contract build 10 | @ls -l target/wasm32-unknown-unknown/release/*.wasm 11 | 12 | fmt: 13 | cargo fmt --all 14 | 15 | clean: 16 | cargo clean 17 | -------------------------------------------------------------------------------- /contracts/stellar/context-proxy/build.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | set -e 3 | 4 | cd "$(dirname $0)" 5 | 6 | TARGET="${CARGO_TARGET_DIR:-../../../target}" 7 | 8 | rustup target add wasm32-unknown-unknown 9 | 10 | cargo build --target wasm32-unknown-unknown --profile app-release 11 | 12 | mkdir -p res 13 | 14 | cp $TARGET/wasm32-unknown-unknown/app-release/calimero_context_proxy_stellar.wasm ./res/ 15 | 16 | if command -v wasm-opt > /dev/null; then 17 | wasm-opt -Oz ./res/calimero_context_proxy_stellar.wasm -o ./res/calimero_context_proxy_stellar.wasm 18 | fi 19 | -------------------------------------------------------------------------------- /contracts/stellar/context-proxy/build_contracts.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | set -e 3 | 4 | cd "$(dirname $0)" 5 | 6 | ./build.sh 7 | ./mock_external/build.sh 8 | -------------------------------------------------------------------------------- /contracts/stellar/context-proxy/mock_external/.gitignore: -------------------------------------------------------------------------------- 1 | test_snapshots/ -------------------------------------------------------------------------------- /contracts/stellar/context-proxy/mock_external/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "calimero-mock-external-stellar" 3 | version = "0.0.0" 4 | edition = "2021" 5 | publish = false 6 | 7 | [lib] 8 | crate-type = ["cdylib"] 9 | doctest = false 10 | 11 | [dependencies] 12 | soroban-sdk = { workspace = true } 13 | 14 | [dev-dependencies] 15 | soroban-sdk = { workspace = true, features = ["testutils"] } 16 | -------------------------------------------------------------------------------- /contracts/stellar/context-proxy/mock_external/Makefile: -------------------------------------------------------------------------------- 1 | default: build 2 | 3 | all: test 4 | 5 | test: build 6 | cargo test 7 | 8 | build: 9 | stellar contract build 10 | @ls -l target/wasm32-unknown-unknown/release/*.wasm 11 | 12 | fmt: 13 | cargo fmt --all 14 | 15 | clean: 16 | cargo clean 17 | -------------------------------------------------------------------------------- /contracts/stellar/context-proxy/mock_external/build.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | set -e 3 | 4 | cd "$(dirname $0)" 5 | 6 | TARGET="${CARGO_TARGET_DIR:-../../../../target}" 7 | 8 | rustup target add wasm32-unknown-unknown 9 | 10 | cargo build --target wasm32-unknown-unknown --profile app-release 11 | 12 | mkdir -p res 13 | 14 | cp $TARGET/wasm32-unknown-unknown/app-release/calimero_mock_external_stellar.wasm ./res/ 15 | 16 | if command -v wasm-opt > /dev/null; then 17 | wasm-opt -Oz ./res/calimero_mock_external_stellar.wasm -o ./res/calimero_mock_external_stellar.wasm 18 | fi 19 | -------------------------------------------------------------------------------- /contracts/stellar/context-proxy/src/sys.rs: -------------------------------------------------------------------------------- 1 | use soroban_sdk::{contractimpl, Address, BytesN, Env}; 2 | 3 | use crate::{ 4 | ContextProxyContract, ContextProxyContractArgs, ContextProxyContractClient, StellarProxyError, 5 | }; 6 | 7 | #[contractimpl] 8 | impl ContextProxyContract { 9 | /// Upgrades the proxy contract with new WASM code 10 | /// # Arguments 11 | /// * `wasm_hash` - Hash of the new WASM code to upgrade to 12 | /// * `context_address` - Address of the context configuration contract 13 | /// # Errors 14 | /// * Returns Unauthorized if caller is not the context configuration contract 15 | pub fn upgrade( 16 | env: Env, 17 | wasm_hash: BytesN<32>, 18 | context_address: Address, 19 | ) -> Result<(), StellarProxyError> { 20 | context_address.require_auth(); 21 | 22 | // Get current state 23 | let state = Self::get_state(&env); 24 | 25 | // Check if caller is the context contract 26 | if context_address != state.context_config_id { 27 | return Err(StellarProxyError::Unauthorized); 28 | } 29 | 30 | // Deploy the upgrade 31 | env.deployer().update_current_contract_wasm(wasm_hash); 32 | 33 | Ok(()) 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /crates/config/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "calimero-config" 3 | version = "0.1.1" 4 | authors.workspace = true 5 | edition.workspace = true 6 | repository.workspace = true 7 | license.workspace = true 8 | 9 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 10 | 11 | [dependencies] 12 | bs58.workspace = true 13 | camino = { workspace = true, features = ["serde1"] } 14 | eyre.workspace = true 15 | libp2p-identity = { workspace = true, features = ["peerid", "serde"] } 16 | multiaddr.workspace = true 17 | serde = { workspace = true, features = ["derive"] } 18 | toml.workspace = true 19 | 20 | calimero-context.workspace = true 21 | calimero-server = { workspace = true, features = ["admin"] } 22 | calimero-network.workspace = true 23 | 24 | [lints] 25 | workspace = true 26 | -------------------------------------------------------------------------------- /crates/context/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "calimero-context" 3 | version = "0.1.0" 4 | authors.workspace = true 5 | edition.workspace = true 6 | repository.workspace = true 7 | license.workspace = true 8 | 9 | [dependencies] 10 | camino = { workspace = true, features = ["serde1"] } 11 | eyre.workspace = true 12 | futures-util.workspace = true 13 | rand.workspace = true 14 | reqwest = { workspace = true, features = ["stream"] } 15 | serde.workspace = true 16 | tokio = { workspace = true, features = ["sync", "macros"] } 17 | tokio-util.workspace = true 18 | tracing.workspace = true 19 | 20 | calimero-context-config = { workspace = true, features = ["client"] } 21 | calimero-blobstore.workspace = true 22 | calimero-primitives = { workspace = true, features = ["borsh", "rand"] } 23 | calimero-network.workspace = true 24 | calimero-node-primitives.workspace = true 25 | calimero-store = { workspace = true, features = ["datatypes"] } 26 | 27 | [lints] 28 | workspace = true 29 | -------------------------------------------------------------------------------- /crates/context/config/src/client/env/config.rs: -------------------------------------------------------------------------------- 1 | use crate::client::{CallClient, Environment}; 2 | 3 | mod mutate; 4 | mod query; 5 | mod types; 6 | use mutate::ContextConfigMutate; 7 | use query::ContextConfigQuery; 8 | 9 | #[derive(Copy, Clone, Debug)] 10 | pub enum ContextConfig {} 11 | 12 | impl<'a, T: 'a> Environment<'a, T> for ContextConfig { 13 | type Query = ContextConfigQuery<'a, T>; 14 | type Mutate = ContextConfigMutate<'a, T>; 15 | 16 | fn query(client: CallClient<'a, T>) -> Self::Query { 17 | ContextConfigQuery { client } 18 | } 19 | 20 | fn mutate(client: CallClient<'a, T>) -> Self::Mutate { 21 | ContextConfigMutate { client } 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /crates/context/config/src/client/env/config/types.rs: -------------------------------------------------------------------------------- 1 | pub mod starknet; 2 | -------------------------------------------------------------------------------- /crates/context/config/src/client/env/proxy.rs: -------------------------------------------------------------------------------- 1 | use crate::client::{CallClient, Environment}; 2 | 3 | mod mutate; 4 | mod query; 5 | mod types; 6 | use mutate::ContextProxyMutate; 7 | use query::ContextProxyQuery; 8 | pub use types::*; 9 | 10 | #[derive(Copy, Clone, Debug)] 11 | pub enum ContextProxy {} 12 | 13 | impl<'a, T: 'a> Environment<'a, T> for ContextProxy { 14 | type Query = ContextProxyQuery<'a, T>; 15 | type Mutate = ContextProxyMutate<'a, T>; 16 | 17 | fn query(client: CallClient<'a, T>) -> Self::Query { 18 | ContextProxyQuery { client } 19 | } 20 | 21 | fn mutate(client: CallClient<'a, T>) -> Self::Mutate { 22 | ContextProxyMutate { client } 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /crates/context/config/src/client/env/proxy/mutate/methods.rs: -------------------------------------------------------------------------------- 1 | use super::{ContextProxyMutate, ContextProxyMutateRequest}; 2 | use crate::repr::Repr; 3 | use crate::types::{ProposalId, SignerId}; 4 | use crate::{Proposal, ProposalAction, ProposalApprovalWithSigner, ProxyMutateRequest}; 5 | 6 | impl<'a, T> ContextProxyMutate<'a, T> { 7 | pub fn propose( 8 | self, 9 | proposal_id: ProposalId, 10 | author_id: SignerId, 11 | actions: Vec, 12 | ) -> ContextProxyMutateRequest<'a, T> { 13 | ContextProxyMutateRequest { 14 | client: self.client, 15 | raw_request: ProxyMutateRequest::Propose { 16 | proposal: Proposal { 17 | id: Repr::new(proposal_id), 18 | author_id: Repr::new(author_id), 19 | actions, 20 | }, 21 | }, 22 | } 23 | } 24 | 25 | pub fn approve( 26 | self, 27 | signer_id: SignerId, 28 | proposal_id: ProposalId, 29 | ) -> ContextProxyMutateRequest<'a, T> { 30 | ContextProxyMutateRequest { 31 | client: self.client, 32 | raw_request: ProxyMutateRequest::Approve { 33 | approval: ProposalApprovalWithSigner { 34 | proposal_id: Repr::new(proposal_id), 35 | signer_id: Repr::new(signer_id), 36 | added_timestamp: 0, // TODO: add timestamp 37 | }, 38 | }, 39 | } 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /crates/context/config/src/client/env/proxy/types.rs: -------------------------------------------------------------------------------- 1 | pub mod starknet; 2 | -------------------------------------------------------------------------------- /crates/context/config/src/client/protocol.rs: -------------------------------------------------------------------------------- 1 | pub mod icp; 2 | pub mod near; 3 | pub mod starknet; 4 | pub mod stellar; 5 | 6 | pub trait Protocol { 7 | const PROTOCOL: &'static str; 8 | } 9 | -------------------------------------------------------------------------------- /crates/context/config/src/client/utils.rs: -------------------------------------------------------------------------------- 1 | use core::fmt::{self, Write}; 2 | 3 | pub fn humanize_iter(iter: I) -> String 4 | where 5 | I: IntoIterator, 6 | { 7 | let mut res = String::new(); 8 | 9 | let mut iter = iter.into_iter().peekable(); 10 | 11 | while let Some(item) = iter.next() { 12 | if !res.is_empty() { 13 | if iter.peek().is_some() { 14 | res.push_str(", "); 15 | } else { 16 | res.push_str(" and "); 17 | } 18 | } 19 | 20 | write!(res, "`{}`", item).expect("infallible"); 21 | } 22 | 23 | res 24 | } 25 | 26 | #[test] 27 | fn test_humanize_iter() { 28 | assert_eq!( 29 | humanize_iter(&["near", "starknet", "ethereum", "solana"]), 30 | "`near`, `starknet`, `ethereum` and `solana`" 31 | ); 32 | assert_eq!( 33 | humanize_iter(&["polkadot", "near", "icp"]), 34 | "`polkadot`, `near` and `icp`" 35 | ); 36 | assert_eq!(humanize_iter(&["this", "that"]), "`this` and `that`"); 37 | assert_eq!(humanize_iter(&["me"]), "`me`"); 38 | assert_eq!(humanize_iter::<[u8; 0]>([]), ""); 39 | } 40 | -------------------------------------------------------------------------------- /crates/context/src/config.rs: -------------------------------------------------------------------------------- 1 | #![allow(clippy::exhaustive_structs, reason = "TODO: Allowed until reviewed")] 2 | 3 | use calimero_context_config::client::config::ClientConfig; 4 | use serde::{Deserialize, Serialize}; 5 | 6 | #[derive(Clone, Debug, Deserialize, Serialize)] 7 | pub struct ContextConfig { 8 | #[serde(rename = "config")] 9 | pub client: ClientConfig, 10 | } 11 | -------------------------------------------------------------------------------- /crates/crypto/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "calimero-crypto" 3 | version = "0.1.0" 4 | authors.workspace = true 5 | edition.workspace = true 6 | repository.workspace = true 7 | license.workspace = true 8 | 9 | [dependencies] 10 | curve25519-dalek.workspace = true 11 | ed25519-dalek = { workspace = true, features = ["rand_core"] } 12 | ring.workspace = true 13 | 14 | calimero-primitives = { workspace = true, features = ["rand"] } 15 | 16 | [dev-dependencies] 17 | eyre.workspace = true 18 | rand.workspace = true 19 | 20 | [lints] 21 | workspace = true 22 | -------------------------------------------------------------------------------- /crates/meroctl/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "meroctl" 3 | version = "0.3.1" 4 | authors.workspace = true 5 | edition.workspace = true 6 | repository.workspace = true 7 | license.workspace = true 8 | 9 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 10 | 11 | [dependencies] 12 | bs58.workspace = true 13 | camino = { workspace = true, features = ["serde1"] } 14 | chrono.workspace = true 15 | clap = { workspace = true, features = ["env", "derive"] } 16 | color-eyre.workspace = true 17 | const_format.workspace = true 18 | dirs.workspace = true 19 | eyre.workspace = true 20 | futures-util.workspace = true 21 | libp2p.workspace = true 22 | notify.workspace = true 23 | rand.workspace = true 24 | reqwest = { workspace = true, features = ["json"] } 25 | serde = { workspace = true, features = ["derive"] } 26 | serde_json.workspace = true 27 | thiserror.workspace = true 28 | tokio = { workspace = true, features = ["io-std", "macros"] } 29 | tokio-tungstenite.workspace = true 30 | url = { workspace = true, features = ["serde"] } 31 | 32 | calimero-config.workspace = true 33 | calimero-primitives.workspace = true 34 | calimero-server = { workspace = true, features = ["jsonrpc", "websocket", "admin"] } 35 | calimero-server-primitives.workspace = true 36 | 37 | [lints] 38 | workspace = true 39 | -------------------------------------------------------------------------------- /crates/meroctl/src/cli/app/get.rs: -------------------------------------------------------------------------------- 1 | use calimero_server_primitives::admin::GetApplicationResponse; 2 | use clap::{Parser, ValueEnum}; 3 | use eyre::Result as EyreResult; 4 | use reqwest::Client; 5 | 6 | use crate::cli::Environment; 7 | use crate::common::{do_request, fetch_multiaddr, load_config, multiaddr_to_url, RequestType}; 8 | use crate::output::Report; 9 | 10 | #[derive(Parser, Debug)] 11 | #[command(about = "Fetch application details")] 12 | pub struct GetCommand { 13 | #[arg(value_name = "APP_ID", help = "application_id of the application")] 14 | pub app_id: String, 15 | } 16 | 17 | #[derive(ValueEnum, Debug, Clone)] 18 | pub enum GetValues { 19 | Details, 20 | } 21 | 22 | impl Report for GetApplicationResponse { 23 | fn report(&self) { 24 | match self.data.application { 25 | Some(ref application) => application.report(), 26 | None => println!("No application found"), 27 | } 28 | } 29 | } 30 | 31 | impl GetCommand { 32 | pub async fn run(self, environment: &Environment) -> EyreResult<()> { 33 | let config = load_config(&environment.args.home, &environment.args.node_name)?; 34 | 35 | let url = multiaddr_to_url( 36 | fetch_multiaddr(&config)?, 37 | &format!("admin-api/dev/applications/{}", self.app_id), 38 | )?; 39 | 40 | let response: GetApplicationResponse = do_request( 41 | &Client::new(), 42 | url, 43 | None::<()>, 44 | &config.identity, 45 | RequestType::Get, 46 | ) 47 | .await?; 48 | 49 | environment.output.write(&response); 50 | 51 | Ok(()) 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /crates/meroctl/src/cli/app/list.rs: -------------------------------------------------------------------------------- 1 | use calimero_server_primitives::admin::ListApplicationsResponse; 2 | use clap::Parser; 3 | use eyre::Result as EyreResult; 4 | use reqwest::Client; 5 | 6 | use crate::cli::Environment; 7 | use crate::common::{do_request, fetch_multiaddr, load_config, multiaddr_to_url, RequestType}; 8 | use crate::output::Report; 9 | 10 | #[derive(Debug, Parser)] 11 | #[command(about = "List installed applications")] 12 | pub struct ListCommand; 13 | 14 | impl Report for ListApplicationsResponse { 15 | fn report(&self) { 16 | for application in &self.data.apps { 17 | application.report(); 18 | } 19 | } 20 | } 21 | 22 | impl ListCommand { 23 | pub async fn run(self, environment: &Environment) -> EyreResult<()> { 24 | let config = load_config(&environment.args.home, &environment.args.node_name)?; 25 | 26 | let response: ListApplicationsResponse = do_request( 27 | &Client::new(), 28 | multiaddr_to_url(fetch_multiaddr(&config)?, "admin-api/dev/applications")?, 29 | None::<()>, 30 | &config.identity, 31 | RequestType::Get, 32 | ) 33 | .await?; 34 | 35 | environment.output.write(&response); 36 | 37 | Ok(()) 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /crates/meroctl/src/cli/bootstrap.rs: -------------------------------------------------------------------------------- 1 | use clap::{Parser, Subcommand}; 2 | use const_format::concatcp; 3 | use eyre::Result as EyreResult; 4 | use start::StartBootstrapCommand; 5 | 6 | use super::Environment; 7 | 8 | mod start; 9 | 10 | pub const EXAMPLES: &str = r" 11 | # Setup and run 2 nodes with demo app 12 | $ meroctl -- --node-name node1 bootstrap start --merod-path /path/to/merod 13 | 14 | # Setup and run 2 nodes with provided app 15 | $ meroctl -- --node-name node1 bootstrap start --merod-path /path/to/merod --app-path /path/to/app 16 | 17 | "; 18 | 19 | #[derive(Debug, Parser)] 20 | #[command(about = "Command for starting bootstrap")] 21 | #[command(after_help = concatcp!( 22 | "Examples:", 23 | EXAMPLES 24 | ))] 25 | pub struct BootstrapCommand { 26 | #[command(subcommand)] 27 | pub subcommand: BootstrapSubCommands, 28 | } 29 | 30 | #[derive(Debug, Subcommand)] 31 | pub enum BootstrapSubCommands { 32 | Start(StartBootstrapCommand), 33 | } 34 | 35 | impl BootstrapCommand { 36 | pub async fn run(self, environment: &Environment) -> EyreResult<()> { 37 | match self.subcommand { 38 | BootstrapSubCommands::Start(generate) => generate.run(environment).await, 39 | } 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /crates/meroctl/src/cli/context/delete.rs: -------------------------------------------------------------------------------- 1 | use calimero_server_primitives::admin::DeleteContextResponse; 2 | use clap::Parser; 3 | use eyre::Result as EyreResult; 4 | use reqwest::Client; 5 | 6 | use crate::cli::Environment; 7 | use crate::common::{do_request, fetch_multiaddr, load_config, multiaddr_to_url, RequestType}; 8 | use crate::output::Report; 9 | 10 | #[derive(Debug, Parser)] 11 | #[command(about = "Delete an context")] 12 | pub struct DeleteCommand { 13 | #[clap(name = "CONTEXT_ID", help = "The context ID to delete")] 14 | pub context_id: String, 15 | } 16 | 17 | impl Report for DeleteContextResponse { 18 | fn report(&self) { 19 | println!("is_deleted: {}", self.data.is_deleted); 20 | } 21 | } 22 | 23 | impl DeleteCommand { 24 | pub async fn run(self, environment: &Environment) -> EyreResult<()> { 25 | let config = load_config(&environment.args.home, &environment.args.node_name)?; 26 | 27 | let url = multiaddr_to_url( 28 | fetch_multiaddr(&config)?, 29 | &format!("admin-api/dev/contexts/{}", self.context_id), 30 | )?; 31 | 32 | let response: DeleteContextResponse = do_request( 33 | &Client::new(), 34 | url, 35 | None::<()>, 36 | &config.identity, 37 | RequestType::Delete, 38 | ) 39 | .await?; 40 | 41 | environment.output.write(&response); 42 | 43 | Ok(()) 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /crates/meroctl/src/cli/context/list.rs: -------------------------------------------------------------------------------- 1 | use calimero_server_primitives::admin::GetContextsResponse; 2 | use clap::Parser; 3 | use eyre::Result as EyreResult; 4 | use reqwest::Client; 5 | 6 | use crate::cli::Environment; 7 | use crate::common::{do_request, fetch_multiaddr, load_config, multiaddr_to_url, RequestType}; 8 | use crate::output::Report; 9 | 10 | #[derive(Debug, Parser)] 11 | #[command(about = "List all contexts")] 12 | pub struct ListCommand; 13 | 14 | impl Report for GetContextsResponse { 15 | fn report(&self) { 16 | for context in &self.data.contexts { 17 | context.report(); 18 | } 19 | } 20 | } 21 | 22 | impl ListCommand { 23 | pub async fn run(self, environment: &Environment) -> EyreResult<()> { 24 | let config = load_config(&environment.args.home, &environment.args.node_name)?; 25 | 26 | let response: GetContextsResponse = do_request( 27 | &Client::new(), 28 | multiaddr_to_url(fetch_multiaddr(&config)?, "admin-api/dev/contexts")?, 29 | None::<()>, 30 | &config.identity, 31 | RequestType::Get, 32 | ) 33 | .await?; 34 | 35 | environment.output.write(&response); 36 | 37 | Ok(()) 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /crates/meroctl/src/cli/identity.rs: -------------------------------------------------------------------------------- 1 | use clap::{Parser, Subcommand}; 2 | use const_format::concatcp; 3 | use eyre::Result as EyreResult; 4 | 5 | use crate::cli::identity::generate::GenerateCommand; 6 | use crate::cli::Environment; 7 | 8 | mod generate; 9 | 10 | pub const EXAMPLES: &str = r" 11 | # 12 | $ meroctl -- --node-name node1 identity generate 13 | "; 14 | 15 | #[derive(Debug, Parser)] 16 | #[command(about = "Command for managing applications")] 17 | #[command(after_help = concatcp!( 18 | "Examples:", 19 | EXAMPLES 20 | ))] 21 | pub struct IdentityCommand { 22 | #[command(subcommand)] 23 | pub subcommand: IdentitySubCommands, 24 | } 25 | 26 | #[derive(Debug, Subcommand)] 27 | pub enum IdentitySubCommands { 28 | Generate(GenerateCommand), 29 | } 30 | 31 | impl IdentityCommand { 32 | pub async fn run(self, environment: &Environment) -> EyreResult<()> { 33 | match self.subcommand { 34 | IdentitySubCommands::Generate(generate) => generate.run(environment).await, 35 | } 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /crates/meroctl/src/cli/identity/generate.rs: -------------------------------------------------------------------------------- 1 | use calimero_server_primitives::admin::GenerateContextIdentityResponse; 2 | use clap::Parser; 3 | use eyre::Result as EyreResult; 4 | use reqwest::Client; 5 | 6 | use crate::cli::Environment; 7 | use crate::common::{do_request, fetch_multiaddr, load_config, multiaddr_to_url, RequestType}; 8 | use crate::output::Report; 9 | 10 | #[derive(Debug, Parser)] 11 | #[command(about = "Generate public/private key pair used for context identity")] 12 | pub struct GenerateCommand; 13 | 14 | impl Report for GenerateContextIdentityResponse { 15 | fn report(&self) { 16 | println!("public_key: {}", self.data.public_key); 17 | println!("private_key: {}", self.data.private_key); 18 | } 19 | } 20 | 21 | impl GenerateCommand { 22 | pub async fn run(self, environment: &Environment) -> EyreResult<()> { 23 | let config = load_config(&environment.args.home, &environment.args.node_name)?; 24 | 25 | let url = multiaddr_to_url(fetch_multiaddr(&config)?, "admin-api/dev/identity/context")?; 26 | 27 | let response: GenerateContextIdentityResponse = do_request( 28 | &Client::new(), 29 | url, 30 | None::<()>, 31 | &config.identity, 32 | RequestType::Post, 33 | ) 34 | .await?; 35 | 36 | environment.output.write(&response); 37 | 38 | Ok(()) 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /crates/meroctl/src/cli/peers.rs: -------------------------------------------------------------------------------- 1 | use calimero_server_primitives::admin::GetPeersCountResponse; 2 | use clap::Parser; 3 | use const_format::concatcp; 4 | use eyre::Result as EyreResult; 5 | use reqwest::Client; 6 | 7 | use crate::cli::Environment; 8 | use crate::common::{do_request, fetch_multiaddr, load_config, multiaddr_to_url, RequestType}; 9 | use crate::output::Report; 10 | 11 | pub const EXAMPLES: &str = r" 12 | # 13 | $ meroctl -- --node-name node1 peers 14 | "; 15 | 16 | #[derive(Debug, Parser)] 17 | #[command(about = "Return the number of connected peers")] 18 | #[command(after_help = concatcp!( 19 | "Examples:", 20 | EXAMPLES 21 | ))] 22 | pub struct PeersCommand; 23 | 24 | impl Report for GetPeersCountResponse { 25 | fn report(&self) { 26 | println!("{}", self.count); 27 | } 28 | } 29 | 30 | impl PeersCommand { 31 | pub async fn run(&self, environment: &Environment) -> EyreResult<()> { 32 | let config = load_config(&environment.args.home, &environment.args.node_name)?; 33 | 34 | let response: GetPeersCountResponse = do_request( 35 | &Client::new(), 36 | multiaddr_to_url(fetch_multiaddr(&config)?, "admin-api/dev/peers")?, 37 | None::<()>, 38 | &config.identity, 39 | RequestType::Get, 40 | ) 41 | .await?; 42 | 43 | environment.output.write(&response); 44 | 45 | Ok(()) 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /crates/meroctl/src/cli/proxy.rs: -------------------------------------------------------------------------------- 1 | use calimero_server::admin::handlers::proposals::Proposal; 2 | use clap::{Parser, Subcommand}; 3 | use eyre::Result as EyreResult; 4 | 5 | use super::Environment; 6 | use crate::output::Report; 7 | 8 | mod get; 9 | use get::GetCommand; 10 | 11 | #[derive(Debug, Parser)] 12 | #[command(about = "Command for managing proxy contract")] 13 | pub struct ProxyCommand { 14 | #[command(subcommand)] 15 | pub subcommand: ProxySubCommands, 16 | } 17 | 18 | #[derive(Debug, Subcommand)] 19 | pub enum ProxySubCommands { 20 | Get(GetCommand), 21 | } 22 | 23 | impl Report for Proposal { 24 | fn report(&self) { 25 | println!("{}", self.id); 26 | println!("{:#?}", self.author); 27 | println!("{}", self.title); 28 | println!("{}", self.description); 29 | } 30 | } 31 | 32 | impl ProxyCommand { 33 | pub async fn run(self, environment: &Environment) -> EyreResult<()> { 34 | match self.subcommand { 35 | ProxySubCommands::Get(get) => get.run(environment).await, 36 | } 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /crates/meroctl/src/defaults.rs: -------------------------------------------------------------------------------- 1 | use camino::{Utf8Path, Utf8PathBuf}; 2 | use dirs::home_dir; 3 | 4 | pub const DEFAULT_CALIMERO_HOME: &str = ".calimero"; 5 | 6 | pub fn default_node_dir() -> Utf8PathBuf { 7 | if let Some(home) = home_dir() { 8 | let home = Utf8Path::from_path(&home).expect("invalid home directory"); 9 | return home.join(DEFAULT_CALIMERO_HOME); 10 | } 11 | 12 | Utf8PathBuf::default() 13 | } 14 | -------------------------------------------------------------------------------- /crates/meroctl/src/main.rs: -------------------------------------------------------------------------------- 1 | use std::process::ExitCode; 2 | 3 | use calimero_server as _; 4 | use clap::Parser; 5 | 6 | use crate::cli::RootCommand; 7 | 8 | mod cli; 9 | mod common; 10 | mod defaults; 11 | mod output; 12 | 13 | #[tokio::main] 14 | async fn main() -> ExitCode { 15 | if let Err(err) = color_eyre::install() { 16 | eprintln!("Failed to install color_eyre: {err}"); 17 | return ExitCode::FAILURE; 18 | } 19 | 20 | let command = RootCommand::parse(); 21 | 22 | match command.run().await { 23 | Ok(()) => ExitCode::SUCCESS, 24 | Err(err) => err.into(), 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /crates/meroctl/src/output.rs: -------------------------------------------------------------------------------- 1 | use clap::ValueEnum; 2 | use color_eyre::owo_colors::OwoColorize; 3 | use serde::Serialize; 4 | 5 | #[derive(Clone, Copy, Debug, Default, ValueEnum)] 6 | pub enum Format { 7 | Json, 8 | #[default] 9 | PlainText, 10 | } 11 | 12 | #[derive(Debug, Default)] 13 | pub struct Output { 14 | format: Format, 15 | } 16 | 17 | pub trait Report { 18 | fn report(&self); 19 | } 20 | 21 | impl Output { 22 | pub const fn new(output_type: Format) -> Self { 23 | Self { 24 | format: output_type, 25 | } 26 | } 27 | 28 | pub fn write(&self, value: &T) { 29 | match self.format { 30 | Format::Json => match serde_json::to_string(&value) { 31 | Ok(json) => println!("{json}"), 32 | Err(err) => eprintln!("Failed to serialize to JSON: {err}"), 33 | }, 34 | Format::PlainText => value.report(), 35 | } 36 | } 37 | } 38 | 39 | #[derive(Clone, Debug, Serialize)] 40 | pub struct InfoLine<'a>(pub &'a str); 41 | 42 | impl Report for InfoLine<'_> { 43 | fn report(&self) { 44 | println!("{} {}", "[INFO]".green(), self.0); 45 | } 46 | } 47 | 48 | #[derive(Clone, Debug, Serialize)] 49 | pub struct ErrorLine<'a>(pub &'a str); 50 | 51 | impl Report for ErrorLine<'_> { 52 | fn report(&self) { 53 | println!("{} {}", "[ERROR]".red(), self.0); 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /crates/merod/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "merod" 3 | version = "0.3.1" 4 | authors.workspace = true 5 | edition.workspace = true 6 | repository.workspace = true 7 | license.workspace = true 8 | 9 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 10 | 11 | [dependencies] 12 | axum.workspace = true 13 | camino = { workspace = true, features = ["serde1"] } 14 | clap = { workspace = true, features = ["env", "derive"] } 15 | color-eyre.workspace = true 16 | const_format.workspace = true 17 | ic-agent.workspace = true 18 | dirs.workspace = true 19 | ed25519-consensus.workspace = true 20 | eyre.workspace = true 21 | futures-util.workspace = true 22 | hex.workspace = true 23 | libp2p.workspace = true 24 | multiaddr.workspace = true 25 | near-crypto.workspace = true 26 | rand.workspace = true 27 | starknet.workspace = true 28 | soroban-client.workspace = true 29 | tokio = { workspace = true, features = ["io-std", "macros"] } 30 | toml_edit.workspace = true 31 | tracing.workspace = true 32 | tracing-subscriber = { workspace = true, features = ["env-filter"] } 33 | url = { workspace = true, features = ["serde"] } 34 | 35 | calimero-blobstore.workspace = true 36 | calimero-config.workspace = true 37 | calimero-context.workspace = true 38 | calimero-context-config = { workspace = true, features = ["client"] } 39 | calimero-node.workspace = true 40 | calimero-network.workspace = true 41 | calimero-server = { workspace = true, features = ["jsonrpc", "websocket", "admin"] } 42 | calimero-store.workspace = true 43 | 44 | [lints] 45 | workspace = true 46 | -------------------------------------------------------------------------------- /crates/merod/gen_localnet_configs.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | set -o pipefail 4 | 5 | # Set default NODE_HOME 6 | : ${NODE_HOME:="$HOME/.calimero"} 7 | 8 | # Check if an argument was provided 9 | if [ $# -eq 0 ]; then 10 | echo "Please provide the number of local nodes argument." 11 | exit 1 12 | fi 13 | 14 | # Get the first command line argument 15 | N=$1 16 | 17 | cargo build --bin merod 18 | 19 | # Iterate in a loop N times 20 | for ((i = 1; i <= N; i++)); do 21 | node_home="$HOME/.calimero/node$i" 22 | echo -e "\x1b[1;36m(i)\x1b[39m Initializing Node $i at \x1b[33m$node_home\x1b[0m" 23 | rm -rf "$node_home" 24 | mkdir -p "$node_home" 25 | ./target/debug/merod --home "$NODE_HOME" --node-name "node$i" \ 26 | init --swarm-port $((2427 + $i)) --server-port $((2527 + $i)) \ 27 | | sed 's/^/ \x1b[1;36m|\x1b[0m /' 28 | if [ $? -ne 0 ]; then 29 | echo -e " \x1b[1;31m✖\x1b[39m \x1b[33mNode $i\x1b[39m initialization failed, ensure that the node is not already running.\x1b[0m" 30 | exit 1 31 | fi 32 | echo -e " \x1b[1;32m✔\x1b[39m \x1b[33mNode $i\x1b[39m initialized.\x1b[0m" 33 | done 34 | -------------------------------------------------------------------------------- /crates/merod/src/defaults.rs: -------------------------------------------------------------------------------- 1 | use camino::{Utf8Path, Utf8PathBuf}; 2 | use dirs::home_dir; 3 | use url::Url; 4 | 5 | pub const DEFAULT_CALIMERO_HOME: &str = ".calimero"; 6 | pub const DEFAULT_RELAYER_URL: &str = "http://3.125.79.112:63529"; 7 | 8 | pub fn default_node_dir() -> Utf8PathBuf { 9 | if let Some(home) = home_dir() { 10 | let home = Utf8Path::from_path(&home).expect("invalid home directory"); 11 | return home.join(DEFAULT_CALIMERO_HOME); 12 | } 13 | 14 | Utf8PathBuf::default() 15 | } 16 | 17 | pub fn default_relayer_url() -> Url { 18 | DEFAULT_RELAYER_URL 19 | .parse() 20 | .expect("invalid default relayer URL") 21 | } 22 | -------------------------------------------------------------------------------- /crates/merod/src/main.rs: -------------------------------------------------------------------------------- 1 | use std::env::var; 2 | 3 | use clap::Parser; 4 | use eyre::Result as EyreResult; 5 | use tracing_subscriber::fmt::layer; 6 | use tracing_subscriber::prelude::*; 7 | use tracing_subscriber::{registry, EnvFilter}; 8 | 9 | use crate::cli::RootCommand; 10 | 11 | mod cli; 12 | mod defaults; 13 | 14 | #[tokio::main] 15 | async fn main() -> EyreResult<()> { 16 | setup()?; 17 | 18 | let command = RootCommand::parse(); 19 | 20 | command.run().await 21 | } 22 | 23 | fn setup() -> EyreResult<()> { 24 | registry() 25 | .with(EnvFilter::builder().parse(format!("info,{}", var("RUST_LOG").unwrap_or_default()))?) 26 | .with(layer()) 27 | .init(); 28 | 29 | color_eyre::install() 30 | } 31 | -------------------------------------------------------------------------------- /crates/network/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "calimero-network" 3 | version = "0.1.0" 4 | authors.workspace = true 5 | edition.workspace = true 6 | repository.workspace = true 7 | license.workspace = true 8 | 9 | [dependencies] 10 | bytes.workspace = true 11 | eyre.workspace = true 12 | futures-util.workspace = true 13 | libp2p = { workspace = true, features = [ 14 | "dcutr", 15 | "gossipsub", 16 | "identify", 17 | "kad", 18 | "macros", 19 | "mdns", 20 | "noise", 21 | "ping", 22 | "quic", 23 | "rendezvous", 24 | "relay", 25 | "tokio", 26 | "tcp", 27 | "tls", 28 | "yamux", 29 | ] } 30 | libp2p-stream.workspace = true 31 | multiaddr.workspace = true 32 | owo-colors.workspace = true 33 | serde = { workspace = true, features = ["derive"] } 34 | thiserror.workspace = true 35 | tokio = { workspace = true, features = ["io-util", "macros"] } 36 | tokio-util = { workspace = true, features = ["codec", "compat"] } 37 | tracing.workspace = true 38 | 39 | calimero-primitives.workspace = true 40 | 41 | [dev-dependencies] 42 | tokio-test.workspace = true 43 | 44 | [lints] 45 | workspace = true 46 | -------------------------------------------------------------------------------- /crates/network/src/events/dcutr.rs: -------------------------------------------------------------------------------- 1 | use libp2p::dcutr::Event; 2 | use owo_colors::OwoColorize; 3 | use tracing::debug; 4 | 5 | use super::{EventHandler, EventLoop}; 6 | 7 | impl EventHandler for EventLoop { 8 | async fn handle(&mut self, event: Event) { 9 | debug!("{}: {:?}", "dcutr".yellow(), event); 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /crates/network/src/events/identify.rs: -------------------------------------------------------------------------------- 1 | use libp2p::identify::Event; 2 | use owo_colors::OwoColorize; 3 | use tracing::{debug, error}; 4 | 5 | use super::{EventHandler, EventLoop}; 6 | 7 | impl EventHandler for EventLoop { 8 | async fn handle(&mut self, event: Event) { 9 | debug!("{}: {:?}", "identify".yellow(), event); 10 | 11 | if let Event::Received { peer_id, info } = event { 12 | self.discovery 13 | .state 14 | .update_peer_protocols(&peer_id, &info.protocols); 15 | 16 | if self.discovery.state.is_peer_relay(&peer_id) { 17 | if let Err(err) = self.create_relay_reservation(&peer_id) { 18 | error!(%err, "Failed to handle relay reservation"); 19 | }; 20 | } 21 | 22 | if self.discovery.state.is_peer_rendezvous(&peer_id) { 23 | if let Err(err) = self.rendezvous_discover(&peer_id) { 24 | error!(%err, "Failed to perform rendezvous discovery"); 25 | }; 26 | 27 | if let Err(err) = self.rendezvous_register(&peer_id) { 28 | error!(%err, "Failed to update registration discovery"); 29 | }; 30 | } 31 | } 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /crates/network/src/events/mdns.rs: -------------------------------------------------------------------------------- 1 | use libp2p::mdns::Event; 2 | use owo_colors::OwoColorize; 3 | use tracing::{debug, error}; 4 | 5 | use super::{EventHandler, EventLoop, RelayedMultiaddr}; 6 | use crate::discovery::state::PeerDiscoveryMechanism; 7 | 8 | impl EventHandler for EventLoop { 9 | async fn handle(&mut self, event: Event) { 10 | debug!("{}: {:?}", "mdns".yellow(), event); 11 | 12 | if let Event::Discovered(peers) = event { 13 | for (peer_id, addr) in peers { 14 | if RelayedMultiaddr::try_from(&addr).is_ok() { 15 | // Skip "fake" relayed addresses to avoid OutgoingConnectionError e.g.: 16 | // /ip4/192.168.1.4/udp/4001/quic-v1/p2p/12D3KooWRnt7EmBwrNALhAXAgM151MdH7Ka9tvYS91ZUqnqwpjVg/p2p-circuit/p2p/12D3KooWSUpChB4mHmZNwVV26at6ZsRo25hNBHJRmPa8zfCeT41Y 17 | continue; 18 | } 19 | 20 | self.discovery 21 | .state 22 | .add_peer_discovery_mechanism(&peer_id, PeerDiscoveryMechanism::Mdns); 23 | 24 | debug!(%peer_id, %addr, "Attempting to dial discovered peer via mdns"); 25 | 26 | if let Err(err) = self.swarm.dial(addr) { 27 | error!("Failed to dial peer: {:?}", err); 28 | } 29 | } 30 | } 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /crates/network/src/events/ping.rs: -------------------------------------------------------------------------------- 1 | use libp2p::ping::Event; 2 | use owo_colors::OwoColorize; 3 | use tracing::debug; 4 | 5 | use super::{EventHandler, EventLoop}; 6 | 7 | impl EventHandler for EventLoop { 8 | async fn handle(&mut self, event: Event) { 9 | debug!("{}: {:?}", "ping".yellow(), event); 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /crates/network/src/events/relay.rs: -------------------------------------------------------------------------------- 1 | use libp2p::relay::client::Event; 2 | use owo_colors::OwoColorize; 3 | use tracing::debug; 4 | 5 | use super::{EventHandler, EventLoop}; 6 | 7 | impl EventHandler for EventLoop { 8 | async fn handle(&mut self, event: Event) { 9 | debug!("{}: {:?}", "relay".yellow(), event); 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /crates/network/src/types.rs: -------------------------------------------------------------------------------- 1 | use libp2p::core::transport::ListenerId; 2 | pub use libp2p::gossipsub::{IdentTopic, Message, MessageId, TopicHash}; 3 | pub use libp2p::identity::PeerId; 4 | use multiaddr::Multiaddr; 5 | 6 | use crate::stream::Stream; 7 | 8 | #[derive(Debug)] 9 | #[non_exhaustive] 10 | pub enum NetworkEvent { 11 | ListeningOn { 12 | listener_id: ListenerId, 13 | address: Multiaddr, 14 | }, 15 | Subscribed { 16 | peer_id: PeerId, 17 | topic: TopicHash, 18 | }, 19 | Unsubscribed { 20 | peer_id: PeerId, 21 | topic: TopicHash, 22 | }, 23 | Message { 24 | id: MessageId, 25 | message: Message, 26 | }, 27 | StreamOpened { 28 | peer_id: PeerId, 29 | stream: Box, 30 | }, 31 | } 32 | -------------------------------------------------------------------------------- /crates/node-primitives/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "calimero-node-primitives" 3 | version = "0.1.0" 4 | authors.workspace = true 5 | edition.workspace = true 6 | repository.workspace = true 7 | license.workspace = true 8 | 9 | [dependencies] 10 | serde = { workspace = true, features = ["derive"] } 11 | thiserror.workspace = true 12 | tokio = { workspace = true, features = ["sync"] } 13 | 14 | calimero-primitives.workspace = true 15 | # TODO: extract runtime primitives to a separate crate 16 | calimero-runtime.workspace = true 17 | 18 | [lints] 19 | workspace = true 20 | -------------------------------------------------------------------------------- /crates/node/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "calimero-node" 3 | version = "0.1.0" 4 | authors.workspace = true 5 | edition.workspace = true 6 | repository.workspace = true 7 | license.workspace = true 8 | 9 | [dependencies] 10 | borsh.workspace = true 11 | camino = { workspace = true, features = ["serde1"] } 12 | clap = { workspace = true, features = ["derive"] } 13 | eyre.workspace = true 14 | futures-util = { workspace = true, features = ["io"] } 15 | libp2p.workspace = true 16 | owo-colors.workspace = true 17 | rand.workspace = true 18 | serde_json.workspace = true 19 | tokio = { workspace = true, features = ["io-std", "macros"] } 20 | tracing.workspace = true 21 | url.workspace = true 22 | 23 | calimero-context.workspace = true 24 | calimero-context-config.workspace = true 25 | calimero-crypto.workspace = true 26 | calimero-blobstore.workspace = true 27 | calimero-network.workspace = true 28 | calimero-node-primitives.workspace = true 29 | calimero-primitives = { workspace = true, features = ["borsh"] } 30 | calimero-runtime.workspace = true 31 | calimero-server = { workspace = true, features = ["jsonrpc", "websocket", "admin"] } 32 | calimero-store = { workspace = true, features = ["datatypes"] } 33 | 34 | [lints] 35 | workspace = true 36 | -------------------------------------------------------------------------------- /crates/node/src/interactive_cli/peers.rs: -------------------------------------------------------------------------------- 1 | use std::sync::Arc; 2 | 3 | use calimero_network::client::NetworkClient; 4 | use calimero_primitives::context::ContextId; 5 | use clap::Parser; 6 | use eyre::Result as EyreResult; 7 | use libp2p::gossipsub::TopicHash; 8 | use owo_colors::OwoColorize; 9 | 10 | /// List the peers in the network 11 | #[derive(Copy, Clone, Debug, Parser)] 12 | pub struct PeersCommand { 13 | /// The context ID to list the peers for 14 | context_id: Option, 15 | } 16 | 17 | impl PeersCommand { 18 | pub async fn run(self, network_client: Arc) -> EyreResult<()> { 19 | let ind = ">>".blue(); 20 | println!( 21 | "{ind} Peers (General): {:#?}", 22 | network_client.peer_count().await.cyan() 23 | ); 24 | 25 | if let Some(context_id) = self.context_id { 26 | let topic = TopicHash::from_raw(context_id); 27 | println!( 28 | "{ind} Peers (Session) for Topic {}: {:#?}", 29 | topic.clone(), 30 | network_client.mesh_peer_count(topic).await.cyan() 31 | ); 32 | } 33 | 34 | Ok(()) 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /crates/primitives/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "calimero-primitives" 3 | version = "0.1.0" 4 | authors.workspace = true 5 | edition.workspace = true 6 | repository.workspace = true 7 | license.workspace = true 8 | 9 | [dependencies] 10 | bs58.workspace = true 11 | borsh = { workspace = true, features = ["derive"], optional = true } 12 | ed25519-dalek.workspace = true 13 | rand = { workspace = true, optional = true } 14 | serde = { workspace = true, features = ["derive"] } 15 | serde_json.workspace = true 16 | sha2.workspace = true 17 | thiserror.workspace = true 18 | url = { workspace = true, features = ["serde"] } 19 | 20 | [dev-dependencies] 21 | hex.workspace = true 22 | 23 | [lints] 24 | workspace = true 25 | -------------------------------------------------------------------------------- /crates/primitives/src/blobs.rs: -------------------------------------------------------------------------------- 1 | use core::fmt::{self, Display, Formatter}; 2 | use core::ops::Deref; 3 | use core::str::FromStr; 4 | 5 | use serde::{Deserialize, Serialize}; 6 | use thiserror::Error as ThisError; 7 | 8 | use crate::hash::{Hash, HashError}; 9 | 10 | #[derive(Copy, Clone, Debug, Deserialize, Eq, Hash, PartialEq, Serialize)] 11 | #[cfg_attr( 12 | feature = "borsh", 13 | derive(borsh::BorshDeserialize, borsh::BorshSerialize) 14 | )] 15 | pub struct BlobId(Hash); 16 | 17 | impl BlobId { 18 | #[must_use] 19 | pub fn as_str(&self) -> &str { 20 | self.0.as_str() 21 | } 22 | } 23 | 24 | impl From<[u8; 32]> for BlobId { 25 | fn from(id: [u8; 32]) -> Self { 26 | Self(id.into()) 27 | } 28 | } 29 | 30 | impl Deref for BlobId { 31 | type Target = [u8; 32]; 32 | 33 | fn deref(&self) -> &Self::Target { 34 | &self.0 35 | } 36 | } 37 | 38 | impl Display for BlobId { 39 | fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { 40 | f.pad(self.as_str()) 41 | } 42 | } 43 | 44 | impl From for String { 45 | fn from(id: BlobId) -> Self { 46 | id.as_str().to_owned() 47 | } 48 | } 49 | 50 | impl From<&BlobId> for String { 51 | fn from(id: &BlobId) -> Self { 52 | id.as_str().to_owned() 53 | } 54 | } 55 | 56 | #[derive(Clone, Copy, Debug, ThisError)] 57 | #[error(transparent)] 58 | pub struct InvalidBlobId(HashError); 59 | 60 | impl FromStr for BlobId { 61 | type Err = InvalidBlobId; 62 | 63 | fn from_str(s: &str) -> Result { 64 | Ok(Self(s.parse().map_err(InvalidBlobId)?)) 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /crates/primitives/src/common.rs: -------------------------------------------------------------------------------- 1 | use serde::{Deserialize, Serialize}; 2 | 3 | #[must_use] 4 | pub const fn bool_true() -> bool { 5 | true 6 | } 7 | 8 | #[derive(Debug, Deserialize, Serialize)] 9 | #[serde(remote = "Result")] 10 | #[expect(clippy::exhaustive_enums, reason = "This cannot have more variants")] 11 | pub enum ResultAlt { 12 | #[serde(rename = "result")] 13 | Ok(T), 14 | #[serde(rename = "error")] 15 | Err(E), 16 | } 17 | 18 | impl From> for Result { 19 | fn from(result: ResultAlt) -> Self { 20 | match result { 21 | ResultAlt::Ok(value) => Ok(value), 22 | ResultAlt::Err(err) => Err(err), 23 | } 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /crates/primitives/src/lib.rs: -------------------------------------------------------------------------------- 1 | pub mod application; 2 | pub mod blobs; 3 | pub mod common; 4 | pub mod context; 5 | pub mod events; 6 | pub mod hash; 7 | pub mod identity; 8 | pub mod reflect; 9 | -------------------------------------------------------------------------------- /crates/primitives/src/tests/hash.rs: -------------------------------------------------------------------------------- 1 | use serde_json::{from_value as from_json_value, json, to_string as to_json_string}; 2 | 3 | use super::*; 4 | 5 | #[test] 6 | fn test_hash_43() { 7 | let hash = Hash::new(b"Hello, World"); 8 | 9 | assert_eq!( 10 | hex::encode(hash.as_bytes()), 11 | "03675ac53ff9cd1535ccc7dfcdfa2c458c5218371f418dc136f2d19ac1fbe8a5" 12 | ); 13 | 14 | assert_eq!(hash.as_str(), "EHdZfnzn717B56XYH8sWLAHfDC3icGEkccNzpAF4PwS"); 15 | assert_eq!( 16 | (*&*&*&*&*&*&hash).as_str(), 17 | "EHdZfnzn717B56XYH8sWLAHfDC3icGEkccNzpAF4PwS" 18 | ); 19 | } 20 | 21 | #[test] 22 | fn test_hash_44() { 23 | let hash = Hash::new(b"Hello World"); 24 | 25 | assert_eq!( 26 | hex::encode(hash.as_bytes()), 27 | "a591a6d40bf420404a011733cfb7b190d62c65bf0bcda32b57b277d9ad9f146e" 28 | ); 29 | 30 | assert_eq!( 31 | hash.as_str(), 32 | "C9K5weED8iiEgM6bkU6gZSgGsV6DW2igMtNtL1sjfFKK" 33 | ); 34 | 35 | assert_eq!( 36 | (*&*&*&*&*&*&hash).as_str(), 37 | "C9K5weED8iiEgM6bkU6gZSgGsV6DW2igMtNtL1sjfFKK" 38 | ); 39 | } 40 | 41 | #[test] 42 | fn test_serde() { 43 | let hash = Hash::new(b"Hello World"); 44 | 45 | assert_eq!( 46 | to_json_string(&hash).unwrap(), 47 | "\"C9K5weED8iiEgM6bkU6gZSgGsV6DW2igMtNtL1sjfFKK\"" 48 | ); 49 | 50 | assert_eq!( 51 | from_json_value::(json!("C9K5weED8iiEgM6bkU6gZSgGsV6DW2igMtNtL1sjfFKK")).unwrap(), 52 | hash 53 | ); 54 | } 55 | -------------------------------------------------------------------------------- /crates/runtime/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "calimero-runtime" 3 | version = "0.1.0" 4 | authors.workspace = true 5 | edition.workspace = true 6 | repository.workspace = true 7 | license.workspace = true 8 | 9 | [dependencies] 10 | borsh = { workspace = true, features = ["derive"] } 11 | fragile.workspace = true 12 | ouroboros.workspace = true 13 | owo-colors = { workspace = true, optional = true } 14 | rand.workspace = true 15 | serde = { workspace = true, features = ["derive"] } 16 | thiserror.workspace = true 17 | ureq.workspace = true 18 | wasmer.workspace = true 19 | wasmer-types.workspace = true 20 | 21 | [[example]] 22 | name = "demo" 23 | 24 | [dev-dependencies] 25 | assert-json-diff.workspace = true 26 | eyre.workspace = true 27 | owo-colors.workspace = true 28 | rand.workspace = true 29 | serde_json.workspace = true 30 | 31 | [features] 32 | host-traces = ["owo-colors"] 33 | 34 | [lints] 35 | workspace = true 36 | -------------------------------------------------------------------------------- /crates/runtime/src/constraint.rs: -------------------------------------------------------------------------------- 1 | use core::marker::PhantomData; 2 | use core::ops::Deref; 3 | 4 | use thiserror::Error as ThisError; 5 | 6 | #[derive(Debug)] 7 | pub struct Constrained { 8 | value: T, 9 | _phantom: PhantomData, 10 | } 11 | 12 | impl Deref for Constrained { 13 | type Target = T; 14 | 15 | fn deref(&self) -> &Self::Target { 16 | &self.value 17 | } 18 | } 19 | 20 | pub trait Constrains { 21 | type Error; 22 | 23 | fn validate(value: T) -> Result; 24 | } 25 | 26 | pub trait Constraint: Sized { 27 | fn validate>(self) -> Result, R::Error>; 28 | } 29 | 30 | impl Constraint for T { 31 | fn validate>(self) -> Result, R::Error> { 32 | Ok(Constrained { 33 | value: R::validate(self)?, 34 | _phantom: PhantomData, 35 | }) 36 | } 37 | } 38 | 39 | #[derive(Debug)] 40 | pub struct MaxU64; 41 | 42 | #[derive(Debug, ThisError)] 43 | #[error("value {0} is greater than the maximum {MAX}")] 44 | pub struct MaxU64Error(u64); 45 | 46 | impl Constrains for MaxU64 { 47 | type Error = MaxU64Error; 48 | 49 | fn validate(value: u64) -> Result { 50 | if value < MAX { 51 | return Ok(value); 52 | } 53 | 54 | Err(MaxU64Error::(value)) 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /crates/runtime/src/logic/errors.rs: -------------------------------------------------------------------------------- 1 | use thiserror::Error as ThisError; 2 | use wasmer::MemoryAccessError; 3 | 4 | use crate::errors::{FunctionCallError, HostError, StorageError, VMRuntimeError}; 5 | 6 | #[derive(Debug, ThisError)] 7 | #[non_exhaustive] 8 | pub enum VMLogicError { 9 | #[error(transparent)] 10 | HostError(#[from] HostError), 11 | #[error(transparent)] 12 | StorageError(StorageError), 13 | } 14 | 15 | impl From for VMLogicError { 16 | fn from(_: MemoryAccessError) -> Self { 17 | Self::HostError(HostError::InvalidMemoryAccess) 18 | } 19 | } 20 | 21 | impl TryFrom for FunctionCallError { 22 | type Error = VMRuntimeError; 23 | 24 | fn try_from(err: VMLogicError) -> Result { 25 | match err { 26 | VMLogicError::StorageError(err) => Err(VMRuntimeError::StorageError(err)), 27 | // todo! is it fine to panic the node on host errors 28 | // todo! because that is a bug in the node, or do we 29 | // todo! include it in the result? and record it on chain 30 | // VMLogicError::HostError(HostError::Panic { 31 | // context: PanicContext::Host, 32 | // message, 33 | // }) => Err(VMRuntimeError::HostError(err)), 34 | VMLogicError::HostError(err) => Ok(Self::HostError(err)), 35 | } 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /crates/runtime/src/store.rs: -------------------------------------------------------------------------------- 1 | use core::fmt::Debug; 2 | use std::collections::BTreeMap; 3 | 4 | pub type Key = Vec; 5 | pub type Value = Vec; 6 | 7 | pub trait Storage: Debug { 8 | fn get(&self, key: &Key) -> Option; 9 | fn set(&mut self, key: Key, value: Value) -> Option; 10 | fn remove(&mut self, key: &Key) -> Option>; 11 | fn has(&self, key: &Key) -> bool; 12 | } 13 | 14 | #[derive(Debug, Default)] 15 | pub struct InMemoryStorage { 16 | inner: BTreeMap, 17 | } 18 | 19 | impl Storage for InMemoryStorage { 20 | fn get(&self, key: &Key) -> Option { 21 | self.inner.get(key).cloned() 22 | } 23 | 24 | fn set(&mut self, key: Key, value: Value) -> Option { 25 | self.inner.insert(key, value) 26 | } 27 | 28 | // todo! revisit this, should we return the value by default? 29 | fn remove(&mut self, key: &Key) -> Option> { 30 | self.inner.remove(key) 31 | } 32 | 33 | fn has(&self, key: &Key) -> bool { 34 | self.inner.contains_key(key) 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /crates/sdk/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "calimero-sdk" 3 | version = "0.1.0" 4 | authors.workspace = true 5 | edition.workspace = true 6 | repository.workspace = true 7 | license.workspace = true 8 | 9 | [dependencies] 10 | borsh = { workspace = true, features = ["derive"] } 11 | bs58.workspace = true 12 | cfg-if.workspace = true 13 | serde = { workspace = true, features = ["derive"] } 14 | serde_json.workspace = true 15 | calimero-sdk-macros.workspace = true 16 | 17 | [dev-dependencies] 18 | trybuild.workspace = true 19 | 20 | [lints] 21 | workspace = true 22 | -------------------------------------------------------------------------------- /crates/sdk/design-guide.md: -------------------------------------------------------------------------------- 1 | # Accountability rules 2 | 3 | - Zero-cost abstractions! 4 | - Do not pull in format code if the user doesn't use it 5 | -------------------------------------------------------------------------------- /crates/sdk/libs/near/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "calimero-sdk-near" 3 | version = "0.1.0" 4 | authors.workspace = true 5 | edition.workspace = true 6 | repository.workspace = true 7 | license.workspace = true 8 | 9 | [dependencies] 10 | near-account-id = { workspace = true, features = ["serde"] } 11 | serde = { workspace = true, features = ["derive"] } 12 | serde_json.workspace = true 13 | serde_with = { workspace = true, features = ["base64"] } 14 | thiserror.workspace = true 15 | 16 | calimero-sdk.workspace = true 17 | calimero-primitives.workspace = true 18 | -------------------------------------------------------------------------------- /crates/sdk/libs/near/src/error.rs: -------------------------------------------------------------------------------- 1 | use serde::{Deserialize, Serialize}; 2 | use serde_json::{Error as JsonError, Value}; 3 | use thiserror::Error as ThisError; 4 | 5 | #[derive(Debug, ThisError)] 6 | pub enum Error { 7 | #[error(transparent)] 8 | JsonError(#[from] JsonError), 9 | 10 | #[error("Failed to fetch: {0}")] 11 | FetchError(String), 12 | 13 | #[error("Server error: {0}")] 14 | ServerError(RpcError), 15 | } 16 | 17 | #[derive(Clone, Debug, Deserialize, PartialEq)] 18 | #[serde(tag = "name", content = "cause", rename_all = "SCREAMING_SNAKE_CASE")] 19 | #[expect(clippy::enum_variant_names)] 20 | pub enum RpcErrorKind { 21 | RequestValidationError(RpcRequestValidationErrorKind), 22 | HandlerError(R), 23 | InternalError(Value), 24 | } 25 | 26 | #[derive(Clone, Debug, Deserialize, PartialEq, Serialize)] 27 | #[serde(tag = "name", content = "info", rename_all = "SCREAMING_SNAKE_CASE")] 28 | pub enum RpcRequestValidationErrorKind { 29 | MethodNotFound { method_name: String }, 30 | ParseError { error_message: String }, 31 | } 32 | 33 | #[derive(Clone, Debug, Deserialize, PartialEq)] 34 | #[serde(deny_unknown_fields)] 35 | pub struct RpcError { 36 | #[serde(flatten)] 37 | pub error_struct: Option>, 38 | pub code: i64, 39 | pub message: String, 40 | pub data: Option, 41 | } 42 | -------------------------------------------------------------------------------- /crates/sdk/libs/near/src/lib.rs: -------------------------------------------------------------------------------- 1 | mod error; 2 | mod jsonrpc; 3 | pub mod query; 4 | pub mod types; 5 | pub mod views; 6 | 7 | pub use error::Error; 8 | pub use jsonrpc::Client; 9 | pub use query::*; 10 | use serde::de::DeserializeOwned; 11 | use serde_json::Value; 12 | pub use types::*; 13 | 14 | pub trait RpcMethod { 15 | type Response: DeserializeOwned; 16 | type Error: DeserializeOwned; 17 | 18 | fn method_name(&self) -> &str; 19 | fn params(&self) -> Value; 20 | } 21 | -------------------------------------------------------------------------------- /crates/sdk/libs/near/src/types.rs: -------------------------------------------------------------------------------- 1 | use calimero_primitives::hash::Hash; 2 | use serde::{Deserialize, Serialize}; 3 | 4 | pub type BlockHeight = u64; 5 | pub type BlockHash = Hash; 6 | pub type AccountId = near_account_id::AccountId; 7 | pub type StorageUsage = u64; 8 | pub type Nonce = u64; 9 | pub type Balance = u128; 10 | pub type ShardId = u64; 11 | 12 | #[derive(Clone, Debug, Deserialize, Serialize)] 13 | #[serde(untagged)] 14 | pub enum BlockId { 15 | Height(BlockHeight), 16 | Hash(BlockHash), 17 | } 18 | -------------------------------------------------------------------------------- /crates/sdk/macros/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "calimero-sdk-macros" 3 | version = "0.1.0" 4 | authors.workspace = true 5 | edition.workspace = true 6 | repository.workspace = true 7 | license.workspace = true 8 | 9 | [lib] 10 | proc-macro = true 11 | 12 | [dependencies] 13 | prettyplease.workspace = true 14 | proc-macro2.workspace = true 15 | quote.workspace = true 16 | syn = { workspace = true, features = ["extra-traits"] } 17 | thiserror.workspace = true 18 | 19 | [features] 20 | nightly = [] 21 | 22 | [lints] 23 | workspace = true 24 | -------------------------------------------------------------------------------- /crates/sdk/macros/src/logic/utils.rs: -------------------------------------------------------------------------------- 1 | use syn::{Path, Type}; 2 | 3 | /// Returns `T` in `T`, `(T)` and an invisible group with T if `!deref`. 4 | /// 5 | /// Also returns T in `&T`, `&mut T`, `(&T)`, `(&mut T)`, `(&T,)`, `(&mut T,)` and groups if `deref` 6 | pub fn typed_path(ty: &Type, deref: bool) -> Option<&Path> { 7 | #[cfg_attr(all(test, feature = "nightly"), deny(non_exhaustive_omitted_patterns))] 8 | #[expect(clippy::wildcard_enum_match_arm, reason = "This is reasonable here")] 9 | match ty { 10 | Type::Path(path) => Some(&path.path), 11 | Type::Reference(reference) if deref => typed_path(&reference.elem, deref), 12 | Type::Group(group) => typed_path(&group.elem, deref), 13 | Type::Paren(paren) => typed_path(&paren.elem, deref), 14 | _ => None, 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /crates/sdk/src/lib.rs: -------------------------------------------------------------------------------- 1 | pub use {borsh, serde, serde_json}; 2 | 3 | pub mod env; 4 | pub mod event; 5 | mod returns; 6 | pub mod state; 7 | mod sys; 8 | pub mod types; 9 | 10 | use core::result::Result as CoreResult; 11 | 12 | pub type Result = CoreResult; 13 | 14 | pub mod app { 15 | pub use calimero_sdk_macros::{destroy, emit, event, init, logic, state}; 16 | } 17 | 18 | #[doc(hidden)] 19 | pub mod __private { 20 | pub use crate::returns::{IntoResult, WrappedReturn}; 21 | } 22 | 23 | #[cfg(test)] 24 | mod integration_tests_package_usage { 25 | use trybuild as _; 26 | } 27 | -------------------------------------------------------------------------------- /crates/sdk/src/state.rs: -------------------------------------------------------------------------------- 1 | use borsh::{BorshDeserialize, BorshSerialize}; 2 | 3 | use crate::event::AppEvent; 4 | 5 | pub trait AppState: BorshSerialize + BorshDeserialize + AppStateInit { 6 | type Event<'a>: AppEvent + 'a; 7 | } 8 | 9 | pub trait Identity {} 10 | 11 | impl Identity for T {} 12 | 13 | #[diagnostic::on_unimplemented( 14 | message = "(calimero)> no method named `#[app::init]` found for type `{Self}`", 15 | label = "add an `#[app::init]` method to this type" 16 | )] 17 | pub trait AppStateInit: Sized { 18 | type Return: Identity; 19 | } 20 | -------------------------------------------------------------------------------- /crates/sdk/src/sys/types.rs: -------------------------------------------------------------------------------- 1 | mod bool; 2 | mod buffer; 3 | mod event; 4 | mod location; 5 | mod pointer; 6 | mod register; 7 | 8 | pub use bool::*; 9 | pub use buffer::*; 10 | pub use event::*; 11 | pub use location::*; 12 | pub use pointer::*; 13 | pub use register::*; 14 | 15 | #[repr(C, u64)] 16 | #[derive(Clone, Copy, Debug, Eq, PartialEq)] 17 | pub enum ValueReturn<'a> { 18 | Ok(Buffer<'a>), 19 | Err(Buffer<'a>), 20 | } 21 | 22 | impl<'a, T, E> From> for ValueReturn<'a> 23 | where 24 | T: AsRef<[u8]>, 25 | E: AsRef<[u8]>, 26 | { 27 | #[inline] 28 | fn from(result: Result<&'a T, &'a E>) -> Self { 29 | match result { 30 | Ok(value) => ValueReturn::Ok(Buffer::new(value)), 31 | Err(value) => ValueReturn::Err(Buffer::new(value)), 32 | } 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /crates/sdk/src/sys/types/bool.rs: -------------------------------------------------------------------------------- 1 | #[repr(C)] 2 | #[derive(Copy, Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)] 3 | pub struct Bool(u32); 4 | 5 | impl TryFrom for bool { 6 | type Error = u32; 7 | 8 | #[inline] 9 | fn try_from(value: Bool) -> Result { 10 | match value { 11 | Bool(0) => Ok(false), 12 | Bool(1) => Ok(true), 13 | Bool(x) => Err(x), 14 | } 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /crates/sdk/src/sys/types/event.rs: -------------------------------------------------------------------------------- 1 | use super::Buffer; 2 | 3 | #[repr(C)] 4 | pub struct Event<'a> { 5 | kind: Buffer<'a>, 6 | data: Buffer<'a>, 7 | } 8 | 9 | impl<'a> Event<'a> { 10 | #[inline] 11 | pub fn new(kind: &'a str, data: &'a [u8]) -> Self { 12 | Event { 13 | kind: Buffer::new(kind), 14 | data: Buffer::new(data), 15 | } 16 | } 17 | 18 | #[inline] 19 | pub fn kind(&self) -> &str { 20 | self.kind 21 | .try_into() 22 | .expect("this should always be a valid utf8 string") // todo! test if this pulls in format code 23 | } 24 | 25 | #[inline] 26 | pub fn data(&self) -> &[u8] { 27 | &self.data 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /crates/sdk/src/sys/types/location.rs: -------------------------------------------------------------------------------- 1 | use core::panic::Location as PanicLocation; 2 | 3 | use super::Buffer; 4 | 5 | #[repr(C)] 6 | pub struct Location<'a> { 7 | file: Buffer<'a>, 8 | line: u32, 9 | column: u32, 10 | } 11 | 12 | impl Location<'_> { 13 | #[inline] 14 | pub fn unknown() -> Self { 15 | Location { 16 | file: Buffer::empty(), 17 | line: 0, 18 | column: 0, 19 | } 20 | } 21 | 22 | #[track_caller] 23 | #[inline] 24 | pub fn caller() -> Self { 25 | PanicLocation::caller().into() 26 | } 27 | 28 | #[inline] 29 | pub fn file(&self) -> &str { 30 | self.file 31 | .try_into() 32 | .expect("this should always be a valid utf8 string") // todo! test if this pulls in format code 33 | } 34 | 35 | #[inline] 36 | pub const fn line(&self) -> u32 { 37 | self.line 38 | } 39 | 40 | #[inline] 41 | pub const fn column(&self) -> u32 { 42 | self.column 43 | } 44 | } 45 | 46 | impl<'a> From<&'a PanicLocation<'_>> for Location<'a> { 47 | #[inline] 48 | fn from(location: &'a PanicLocation<'_>) -> Self { 49 | Location { 50 | file: Buffer::from(location.file()), 51 | line: location.line(), 52 | column: location.column(), 53 | } 54 | } 55 | } 56 | 57 | impl<'a> From>> for Location<'a> { 58 | #[inline] 59 | fn from(location: Option<&'a PanicLocation<'_>>) -> Self { 60 | location.map_or_else(Location::unknown, Location::from) 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /crates/sdk/src/sys/types/register.rs: -------------------------------------------------------------------------------- 1 | use super::PtrSizedInt; 2 | 3 | #[repr(C)] 4 | #[derive(Copy, Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)] 5 | pub struct RegisterId(PtrSizedInt); 6 | 7 | impl RegisterId { 8 | #[inline] 9 | pub const fn new(value: usize) -> Self { 10 | Self(PtrSizedInt::new(value)) 11 | } 12 | 13 | pub const fn as_usize(self) -> usize { 14 | self.0.as_usize() 15 | } 16 | } 17 | 18 | impl From for RegisterId { 19 | #[inline] 20 | fn from(value: usize) -> Self { 21 | Self::new(value) 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /crates/sdk/src/types.rs: -------------------------------------------------------------------------------- 1 | use core::error::Error as CoreError; 2 | 3 | use serde::{Serialize, Serializer}; 4 | 5 | #[derive(Debug, Serialize)] 6 | pub struct Error(#[serde(serialize_with = "error_string")] Box); 7 | 8 | fn error_string(error: &impl AsRef, serializer: S) -> Result 9 | where 10 | S: Serializer, 11 | { 12 | serializer.serialize_str(&error.as_ref().to_string()) 13 | } 14 | 15 | impl Error { 16 | #[must_use] 17 | pub fn msg(s: &str) -> Self { 18 | Self(s.to_owned().into()) 19 | } 20 | } 21 | 22 | impl From for Error 23 | where 24 | T: CoreError + 'static, 25 | { 26 | fn from(error: T) -> Self { 27 | Self(Box::new(error)) 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /crates/sdk/tests/macros.rs: -------------------------------------------------------------------------------- 1 | #![allow(unused_crate_dependencies, reason = "False positives")] 2 | #![allow( 3 | clippy::tests_outside_test_module, 4 | reason = "Allowable in integration tests" 5 | )] 6 | 7 | #[ignore] 8 | #[test] 9 | fn all() { 10 | let t = trybuild::TestCases::new(); 11 | 12 | // todo! break these up into pass/fail dirs for organization 13 | 14 | t.pass("tests/macros/valid_receivers.rs"); 15 | t.compile_fail("tests/macros/invalid_receivers.rs"); 16 | t.compile_fail("tests/macros/invalid_generics.rs"); 17 | t.pass("tests/macros/valid_generics.rs"); 18 | t.pass("tests/macros/valid_args.rs"); 19 | t.compile_fail("tests/macros/invalid_args.rs"); 20 | t.compile_fail("tests/macros/invalid_methods.rs"); 21 | } 22 | -------------------------------------------------------------------------------- /crates/sdk/tests/macros/invalid_args.rs: -------------------------------------------------------------------------------- 1 | use calimero_sdk::app; 2 | 3 | #[app::state] 4 | struct MyType; 5 | 6 | trait MyTrait {} 7 | 8 | #[app::logic] 9 | impl MyType { 10 | pub fn method(&self, value: impl MyTrait) {} 11 | } 12 | 13 | fn main() {} 14 | -------------------------------------------------------------------------------- /crates/sdk/tests/macros/invalid_generics.rs: -------------------------------------------------------------------------------- 1 | use calimero_sdk::app; 2 | 3 | #[app::state] 4 | struct MyType<'t, T, 'calimero>(&'t &'calimero T); // todo! what happens here? 5 | 6 | #[app::logic] 7 | impl<'t, T, 'calimero> MyType<'t, T, 'calimero> { 8 | // ignored because it's private 9 | fn method0<'k, K, 'v, V>(&self, tag: &'t T, key: &'k K, value: &'v V) {} 10 | pub fn method1<'k, K, 'v, V>(&self, tag: &'t T, key: &'k K, value: &'v V) {} 11 | pub fn method2(&self, arr: [u8; N]) {} 12 | } 13 | 14 | #[app::logic] 15 | impl<'t, T, 'x> MyType<'t, T, 'x> { 16 | pub fn method<'k, 'calimero>(&self, key: &'k str, value: &'calimero str) {} 17 | } 18 | 19 | fn main() {} 20 | -------------------------------------------------------------------------------- /crates/sdk/tests/macros/invalid_generics.stderr: -------------------------------------------------------------------------------- 1 | error: (calimero)> generic types are not supported 2 | --> tests/macros/generics.rs:9:10 3 | | 4 | 9 | impl<'t, T> MyType<'t, T> { 5 | | ^ 6 | 7 | error: (calimero)> generic types are not supported 8 | --> tests/macros/generics.rs:12:24 9 | | 10 | 12 | pub fn method1<'k, K, 'v, V>(&self, tag: &'t T, key: &'k K, value: &'v V) {} 11 | | ^ 12 | 13 | error: (calimero)> generic types are not supported 14 | --> tests/macros/generics.rs:12:31 15 | | 16 | 12 | pub fn method1<'k, K, 'v, V>(&self, tag: &'t T, key: &'k K, value: &'v V) {} 17 | | ^ 18 | 19 | error: (calimero)> generic types are not supported 20 | --> tests/macros/generics.rs:13:20 21 | | 22 | 13 | pub fn method2(&self, arr: [u8; N]) {} 23 | | ^^^^^^^^^^^^^^ 24 | -------------------------------------------------------------------------------- /crates/sdk/tests/macros/invalid_methods.rs: -------------------------------------------------------------------------------- 1 | use calimero_sdk::app; 2 | 3 | #[app::state] 4 | struct MyType; 5 | 6 | #[app::logic] 7 | impl MyType { 8 | pub async fn method_00(&self) {} 9 | pub unsafe fn method_01(&self) {} 10 | } 11 | 12 | fn main() {} 13 | -------------------------------------------------------------------------------- /crates/sdk/tests/macros/invalid_methods.stderr: -------------------------------------------------------------------------------- 1 | error: (calimero)> async methods are not supported 2 | --> tests/macros/invalid_methods.rs:8:9 3 | | 4 | 8 | pub async fn method_00(&self) {} 5 | | ^^^^^ 6 | 7 | error: (calimero)> unsafe methods are not supported 8 | --> tests/macros/invalid_methods.rs:9:9 9 | | 10 | 9 | pub unsafe fn method_01(&self) {} 11 | | ^^^^^^ 12 | -------------------------------------------------------------------------------- /crates/sdk/tests/macros/invalid_receivers.rs: -------------------------------------------------------------------------------- 1 | use calimero_sdk::app; 2 | 3 | #[app::state] 4 | struct MyType<'a>(&'a ()); 5 | 6 | #[app::logic] 7 | impl<'a> MyType<'a> { 8 | pub fn method_00(self) {} 9 | pub fn method_01(self: Self) {} 10 | pub fn method_02(self: (Self)) {} 11 | pub fn method_03(self: (Self,)) {} 12 | pub fn method_04(mut self: Self) {} 13 | pub fn method_05(mut self: (Self)) {} 14 | pub fn method_06(self: &'a (Self,)) {} 15 | pub fn method_07(self: MyType<'a>) {} 16 | pub fn method_08(self: (MyType<'a>)) {} 17 | pub fn method_09(self: (MyType<'a>,)) {} 18 | pub fn method_10(mut self: MyType<'a>) {} 19 | pub fn method_11(mut self: (MyType<'a>)) {} 20 | pub fn method_12(mut self: (MyType<'a>,)) {} 21 | pub fn method_13(self: OtherType) {} 22 | pub fn method_14(self: (OtherType)) {} 23 | pub fn method_15(self: (OtherType,)) {} 24 | pub fn method_16(self: &OtherType) {} 25 | pub fn method_17(self: &(OtherType)) {} 26 | pub fn method_18(self: &(OtherType,)) {} 27 | } 28 | 29 | struct OtherType; 30 | 31 | fn main() {} 32 | -------------------------------------------------------------------------------- /crates/sdk/tests/macros/invalid_state.rs: -------------------------------------------------------------------------------- 1 | #[app::state] 2 | #[app::state()] 3 | #[app::state("")] 4 | #[app::state(emits)] 5 | #[app::state(emits none)] 6 | #[app::state(emits = )] 7 | #[app::state(emits = "")] 8 | #[app::state(emits = Event alt)] 9 | #[app::state(emits = Event<'a>)] 10 | #[app::state(emits = Event<&str>)] 11 | #[app::state(emits = for<>)] 12 | #[app::state(emits = for<'a>)] 13 | #[app::state(emits = for<'a> "")] 14 | #[app::state(emits = for<'a> Event)] 15 | #[app::state(emits = for<'a> Event<'a>)] 16 | #[app::state(emits = for<'a> Event<'a> alt)] 17 | #[derive(BorshDeserialize, BorshSerialize, Default)] 18 | #[borsh(crate = "calimero_sdk::borsh")] 19 | struct MyType { 20 | items: HashMap, 21 | } 22 | 23 | #[app::event] 24 | pub enum Event<'a> { 25 | Inserted { key: &'a str, value: &'a str }, 26 | Updated { key: &'a str, value: &'a str }, 27 | Removed { key: &'a str }, 28 | Cleared, 29 | } 30 | -------------------------------------------------------------------------------- /crates/sdk/tests/macros/valid_args.rs: -------------------------------------------------------------------------------- 1 | use calimero_sdk::app; 2 | 3 | #[app::state] 4 | struct MyType; 5 | 6 | #[app::logic] 7 | impl MyType { 8 | pub fn method_00(&self, key: &str, value: &str) {} 9 | pub fn method_01<'a>(&self, key: &'a str, value: &'a str) {} 10 | pub fn method_02(&self, key: String, value: Self) {} 11 | pub fn method_03<'a>(&self, entries: &'a [(&'a str, &'a Self)]) -> Self {} 12 | } 13 | 14 | fn main() {} 15 | -------------------------------------------------------------------------------- /crates/sdk/tests/macros/valid_generics.rs: -------------------------------------------------------------------------------- 1 | use calimero_sdk::app; 2 | 3 | #[app::state] 4 | struct MyType<'t>; 5 | 6 | #[app::logic] 7 | impl<'t> MyType<'t> { 8 | // ignored because it's private 9 | fn method0<'k, K, 'v, V, 'calimero>( 10 | &self, 11 | tag: &'t T, 12 | key: &'k K, 13 | value: &'v V, 14 | calimero: &'calimero str, 15 | ) { 16 | } 17 | pub fn method<'k, 'v>(&self, tag: &'t str, key: &'k str, value: &'v str) {} 18 | } 19 | 20 | fn main() {} 21 | -------------------------------------------------------------------------------- /crates/server-primitives/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "calimero-server-primitives" 3 | version = "0.1.0" 4 | authors.workspace = true 5 | edition.workspace = true 6 | repository.workspace = true 7 | license.workspace = true 8 | 9 | [dependencies] 10 | camino.workspace = true 11 | eyre.workspace = true 12 | serde = { workspace = true, features = ["derive"] } 13 | serde_json.workspace = true 14 | thiserror.workspace = true 15 | url = { workspace = true, features = ["serde"] } 16 | 17 | calimero-node-primitives.workspace = true 18 | calimero-primitives.workspace = true 19 | 20 | [lints] 21 | workspace = true 22 | -------------------------------------------------------------------------------- /crates/server-primitives/src/lib.rs: -------------------------------------------------------------------------------- 1 | use serde::{Deserialize, Serialize}; 2 | use thiserror::Error as ThisError; 3 | 4 | pub mod admin; 5 | pub mod jsonrpc; 6 | pub mod ws; 7 | 8 | #[derive(Clone, Copy, Debug, Deserialize, Serialize, ThisError)] 9 | #[error("Infallible")] 10 | #[expect(clippy::exhaustive_enums, reason = "This will never have any variants")] 11 | pub enum Infallible {} 12 | -------------------------------------------------------------------------------- /crates/server/src/admin.rs: -------------------------------------------------------------------------------- 1 | pub mod handlers; 2 | pub mod service; 3 | pub mod storage; 4 | pub mod utils; 5 | -------------------------------------------------------------------------------- /crates/server/src/admin/handlers.rs: -------------------------------------------------------------------------------- 1 | pub mod add_client_key; 2 | pub mod applications; 3 | pub mod challenge; 4 | pub mod context; 5 | pub mod did; 6 | pub mod identity; 7 | pub mod peers; 8 | pub mod proposals; 9 | pub mod root_keys; 10 | -------------------------------------------------------------------------------- /crates/server/src/admin/handlers/applications.rs: -------------------------------------------------------------------------------- 1 | pub mod get_application; 2 | pub mod install_application; 3 | pub mod install_dev_application; 4 | pub mod list_applications; 5 | pub mod uninstall_application; 6 | -------------------------------------------------------------------------------- /crates/server/src/admin/handlers/applications/get_application.rs: -------------------------------------------------------------------------------- 1 | use std::sync::Arc; 2 | 3 | use axum::extract::Path; 4 | use axum::http::StatusCode; 5 | use axum::response::IntoResponse; 6 | use axum::Extension; 7 | use calimero_primitives::application::ApplicationId; 8 | use calimero_server_primitives::admin::GetApplicationResponse; 9 | 10 | use crate::admin::service::ApiResponse; 11 | use crate::AdminState; 12 | 13 | pub async fn handler( 14 | Extension(state): Extension>, 15 | Path(application_id): Path, 16 | ) -> impl IntoResponse { 17 | match state.ctx_manager.get_application(&application_id) { 18 | Ok(application) => ApiResponse { 19 | payload: GetApplicationResponse::new(application), 20 | } 21 | .into_response(), 22 | Err(err) => (StatusCode::INTERNAL_SERVER_ERROR, err.to_string()).into_response(), 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /crates/server/src/admin/handlers/applications/install_application.rs: -------------------------------------------------------------------------------- 1 | use std::sync::Arc; 2 | 3 | use axum::http::StatusCode; 4 | use axum::response::IntoResponse; 5 | use axum::{Extension, Json}; 6 | use calimero_server_primitives::admin::{InstallApplicationRequest, InstallApplicationResponse}; 7 | 8 | use crate::admin::service::ApiResponse; 9 | use crate::AdminState; 10 | 11 | pub async fn handler( 12 | Extension(state): Extension>, 13 | Json(req): Json, 14 | ) -> impl IntoResponse { 15 | match state 16 | .ctx_manager 17 | .install_application_from_url(req.url, req.metadata, req.hash) 18 | .await 19 | { 20 | Ok(application_id) => ApiResponse { 21 | payload: InstallApplicationResponse::new(application_id), 22 | } 23 | .into_response(), 24 | Err(err) => (StatusCode::INTERNAL_SERVER_ERROR, err.to_string()).into_response(), 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /crates/server/src/admin/handlers/applications/install_dev_application.rs: -------------------------------------------------------------------------------- 1 | use std::sync::Arc; 2 | 3 | use axum::http::StatusCode; 4 | use axum::response::IntoResponse; 5 | use axum::{Extension, Json}; 6 | use calimero_server_primitives::admin::{InstallApplicationResponse, InstallDevApplicationRequest}; 7 | 8 | use crate::admin::service::ApiResponse; 9 | use crate::AdminState; 10 | 11 | pub async fn handler( 12 | Extension(state): Extension>, 13 | Json(req): Json, 14 | ) -> impl IntoResponse { 15 | match state 16 | .ctx_manager 17 | .install_application_from_path(req.path, req.metadata) 18 | .await 19 | { 20 | Ok(application_id) => ApiResponse { 21 | payload: InstallApplicationResponse::new(application_id), 22 | } 23 | .into_response(), 24 | Err(err) => (StatusCode::INTERNAL_SERVER_ERROR, err.to_string()).into_response(), 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /crates/server/src/admin/handlers/applications/list_applications.rs: -------------------------------------------------------------------------------- 1 | use std::sync::Arc; 2 | 3 | use axum::response::IntoResponse; 4 | use axum::Extension; 5 | use calimero_server_primitives::admin::ListApplicationsResponse; 6 | 7 | use crate::admin::service::{parse_api_error, ApiResponse}; 8 | use crate::AdminState; 9 | 10 | pub async fn handler(Extension(state): Extension>) -> impl IntoResponse { 11 | let applications = state 12 | .ctx_manager 13 | .list_installed_applications() 14 | .map_err(|err| parse_api_error(err).into_response()); 15 | match applications { 16 | Ok(applications) => { 17 | ApiResponse { 18 | payload: ListApplicationsResponse::new(applications), 19 | } 20 | } 21 | .into_response(), 22 | Err(err) => err.into_response(), 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /crates/server/src/admin/handlers/applications/uninstall_application.rs: -------------------------------------------------------------------------------- 1 | use std::sync::Arc; 2 | 3 | use axum::http::StatusCode; 4 | use axum::response::IntoResponse; 5 | use axum::{Extension, Json}; 6 | use calimero_server_primitives::admin::{ 7 | UninstallApplicationRequest, UninstallApplicationResponse, 8 | }; 9 | 10 | use crate::admin::service::ApiResponse; 11 | use crate::AdminState; 12 | 13 | pub async fn handler( 14 | Extension(state): Extension>, 15 | Json(req): Json, 16 | ) -> impl IntoResponse { 17 | match state.ctx_manager.uninstall_application(req.application_id) { 18 | Ok(()) => ApiResponse { 19 | payload: UninstallApplicationResponse::new(req.application_id), 20 | } 21 | .into_response(), 22 | Err(err) => (StatusCode::INTERNAL_SERVER_ERROR, err.to_string()).into_response(), 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /crates/server/src/admin/handlers/context.rs: -------------------------------------------------------------------------------- 1 | pub mod create_context; 2 | pub mod delete_context; 3 | pub mod get_context; 4 | pub mod get_context_client_keys; 5 | pub mod get_context_identities; 6 | pub mod get_context_storage; 7 | pub mod get_contexts; 8 | pub mod invite_to_context; 9 | pub mod join_context; 10 | pub mod update_context_application; 11 | -------------------------------------------------------------------------------- /crates/server/src/admin/handlers/context/create_context.rs: -------------------------------------------------------------------------------- 1 | use std::sync::Arc; 2 | 3 | use axum::response::IntoResponse; 4 | use axum::{Extension, Json}; 5 | use calimero_server_primitives::admin::{CreateContextRequest, CreateContextResponse}; 6 | use tokio::sync::oneshot; 7 | 8 | use crate::admin::service::{parse_api_error, ApiResponse}; 9 | use crate::AdminState; 10 | 11 | pub async fn handler( 12 | Extension(state): Extension>, 13 | Json(req): Json, 14 | ) -> impl IntoResponse { 15 | let (tx, rx) = oneshot::channel(); 16 | 17 | let result = state 18 | .ctx_manager 19 | .create_context( 20 | &req.protocol, 21 | req.context_seed.map(Into::into), 22 | req.application_id, 23 | None, 24 | req.initialization_params, 25 | tx, 26 | ) 27 | .map_err(parse_api_error); 28 | 29 | if let Err(err) = result { 30 | return err.into_response(); 31 | } 32 | 33 | let Ok(result) = rx.await else { 34 | return "internal error".into_response(); 35 | }; 36 | 37 | match result { 38 | Ok((context_id, member_public_key)) => ApiResponse { 39 | payload: CreateContextResponse::new(context_id, member_public_key), 40 | } 41 | .into_response(), 42 | Err(err) => parse_api_error(err).into_response(), 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /crates/server/src/admin/handlers/context/delete_context.rs: -------------------------------------------------------------------------------- 1 | use core::str::FromStr; 2 | use std::sync::Arc; 3 | 4 | use axum::extract::Path; 5 | use axum::response::IntoResponse; 6 | use axum::Extension; 7 | use calimero_primitives::context::ContextId; 8 | use calimero_server_primitives::admin::{DeleteContextResponse, DeletedContextResponseData}; 9 | use reqwest::StatusCode; 10 | use tower_sessions::Session; 11 | 12 | use crate::admin::service::{parse_api_error, ApiError, ApiResponse}; 13 | use crate::AdminState; 14 | 15 | pub async fn handler( 16 | Path(context_id): Path, 17 | _session: Session, 18 | Extension(state): Extension>, 19 | ) -> impl IntoResponse { 20 | let Ok(context_id_result) = ContextId::from_str(&context_id) else { 21 | return ApiError { 22 | status_code: StatusCode::BAD_REQUEST, 23 | message: "Invalid context id".into(), 24 | } 25 | .into_response(); 26 | }; 27 | 28 | // todo! experiment with Interior: WriteLayer 29 | let result = state 30 | .ctx_manager 31 | .delete_context(&context_id_result) 32 | .await 33 | .map_err(parse_api_error); 34 | 35 | match result { 36 | Ok(result) => ApiResponse { 37 | payload: DeleteContextResponse { 38 | data: DeletedContextResponseData { is_deleted: result }, 39 | }, 40 | } 41 | .into_response(), 42 | Err(err) => err.into_response(), 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /crates/server/src/admin/handlers/context/get_context.rs: -------------------------------------------------------------------------------- 1 | use std::sync::Arc; 2 | 3 | use axum::extract::Path; 4 | use axum::response::IntoResponse; 5 | use axum::Extension; 6 | use calimero_primitives::context::ContextId; 7 | use calimero_server_primitives::admin::GetContextResponse; 8 | use reqwest::StatusCode; 9 | 10 | use crate::admin::service::{parse_api_error, ApiError, ApiResponse}; 11 | use crate::AdminState; 12 | 13 | pub async fn handler( 14 | Path(context_id): Path, 15 | Extension(state): Extension>, 16 | ) -> impl IntoResponse { 17 | // todo! experiment with Interior: WriteLayer 18 | let context = state 19 | .ctx_manager 20 | .get_context(&context_id) 21 | .map_err(|err| parse_api_error(err).into_response()); 22 | 23 | #[expect(clippy::option_if_let_else, reason = "Clearer here")] 24 | match context { 25 | Ok(ctx) => match ctx { 26 | Some(context) => ApiResponse { 27 | payload: GetContextResponse { data: context }, 28 | } 29 | .into_response(), 30 | None => ApiError { 31 | status_code: StatusCode::NOT_FOUND, 32 | message: "Context not found".into(), 33 | } 34 | .into_response(), 35 | }, 36 | Err(err) => err.into_response(), 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /crates/server/src/admin/handlers/context/get_context_client_keys.rs: -------------------------------------------------------------------------------- 1 | use std::sync::Arc; 2 | 3 | use axum::extract::Path; 4 | use axum::response::IntoResponse; 5 | use axum::Extension; 6 | use calimero_primitives::context::ContextId; 7 | use calimero_server_primitives::admin::GetContextClientKeysResponse; 8 | 9 | use crate::admin::service::{parse_api_error, ApiResponse}; 10 | use crate::admin::storage::client_keys::get_context_client_key; 11 | use crate::AdminState; 12 | 13 | pub async fn handler( 14 | Path(context_id): Path, 15 | Extension(state): Extension>, 16 | ) -> impl IntoResponse { 17 | // todo! experiment with Interior: WriteLayer 18 | let client_keys_result = get_context_client_key(&state.store.clone(), &context_id) 19 | .map_err(|err| parse_api_error(err).into_response()); 20 | 21 | match client_keys_result { 22 | Ok(client_keys) => ApiResponse { 23 | payload: GetContextClientKeysResponse::new(client_keys), 24 | } 25 | .into_response(), 26 | Err(err) => err.into_response(), 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /crates/server/src/admin/handlers/context/get_context_storage.rs: -------------------------------------------------------------------------------- 1 | use std::sync::Arc; 2 | 3 | use axum::extract::Path; 4 | use axum::response::IntoResponse; 5 | use axum::Extension; 6 | use calimero_server_primitives::admin::GetContextStorageResponse; 7 | 8 | use crate::admin::service::ApiResponse; 9 | use crate::AdminState; 10 | 11 | pub async fn handler( 12 | Path(_context_id): Path, 13 | Extension(_state): Extension>, 14 | ) -> impl IntoResponse { 15 | ApiResponse { 16 | payload: GetContextStorageResponse::new(0), 17 | } 18 | .into_response() 19 | } 20 | -------------------------------------------------------------------------------- /crates/server/src/admin/handlers/context/get_contexts.rs: -------------------------------------------------------------------------------- 1 | use std::sync::Arc; 2 | 3 | use axum::response::IntoResponse; 4 | use axum::Extension; 5 | use calimero_server_primitives::admin::GetContextsResponse; 6 | 7 | use crate::admin::service::{parse_api_error, ApiResponse}; 8 | use crate::AdminState; 9 | 10 | pub async fn handler(Extension(state): Extension>) -> impl IntoResponse { 11 | // todo! experiment with Interior: WriteLayer 12 | let contexts = state 13 | .ctx_manager 14 | .get_contexts(None) 15 | .map_err(parse_api_error); 16 | 17 | match contexts { 18 | Ok(contexts) => ApiResponse { 19 | payload: GetContextsResponse::new(contexts), 20 | } 21 | .into_response(), 22 | Err(err) => err.into_response(), 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /crates/server/src/admin/handlers/context/invite_to_context.rs: -------------------------------------------------------------------------------- 1 | use std::sync::Arc; 2 | 3 | use axum::response::IntoResponse; 4 | use axum::{Extension, Json}; 5 | use calimero_server_primitives::admin::{InviteToContextRequest, InviteToContextResponse}; 6 | 7 | use crate::admin::service::{parse_api_error, ApiResponse}; 8 | use crate::AdminState; 9 | 10 | pub async fn handler( 11 | Extension(state): Extension>, 12 | Json(req): Json, 13 | ) -> impl IntoResponse { 14 | let result = state 15 | .ctx_manager 16 | .invite_to_context(req.context_id, req.inviter_id, req.invitee_id) 17 | .await 18 | .map_err(parse_api_error); 19 | 20 | match result { 21 | Ok(invitation_payload) => ApiResponse { 22 | payload: InviteToContextResponse::new(invitation_payload), 23 | } 24 | .into_response(), 25 | Err(err) => err.into_response(), 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /crates/server/src/admin/handlers/context/join_context.rs: -------------------------------------------------------------------------------- 1 | use std::sync::Arc; 2 | 3 | use axum::response::IntoResponse; 4 | use axum::{Extension, Json}; 5 | use calimero_server_primitives::admin::{JoinContextRequest, JoinContextResponse}; 6 | 7 | use crate::admin::service::{parse_api_error, ApiResponse}; 8 | use crate::AdminState; 9 | 10 | pub async fn handler( 11 | Extension(state): Extension>, 12 | Json(JoinContextRequest { 13 | private_key, 14 | invitation_payload, 15 | }): Json, 16 | ) -> impl IntoResponse { 17 | let result = state 18 | .ctx_manager 19 | .join_context(private_key, invitation_payload) 20 | .await 21 | .map_err(parse_api_error); 22 | 23 | match result { 24 | Ok(result) => ApiResponse { 25 | payload: JoinContextResponse::new(result), 26 | } 27 | .into_response(), 28 | Err(err) => err.into_response(), 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /crates/server/src/admin/handlers/context/update_context_application.rs: -------------------------------------------------------------------------------- 1 | use core::str::FromStr; 2 | use std::sync::Arc; 3 | 4 | use axum::extract::Path; 5 | use axum::response::IntoResponse; 6 | use axum::{Extension, Json}; 7 | use calimero_primitives::context::ContextId; 8 | use calimero_server_primitives::admin::{ 9 | UpdateContextApplicationRequest, UpdateContextApplicationResponse, 10 | }; 11 | use reqwest::StatusCode; 12 | 13 | use crate::admin::service::{parse_api_error, ApiError, ApiResponse}; 14 | use crate::AdminState; 15 | 16 | pub async fn handler( 17 | Extension(state): Extension>, 18 | Path(context_id): Path, 19 | Json(req): Json, 20 | ) -> impl IntoResponse { 21 | let Ok(context_id_result) = ContextId::from_str(&context_id) else { 22 | return ApiError { 23 | status_code: StatusCode::BAD_REQUEST, 24 | message: "Invalid context id".into(), 25 | } 26 | .into_response(); 27 | }; 28 | 29 | let result = state 30 | .ctx_manager 31 | .update_application_id( 32 | context_id_result, 33 | req.application_id, 34 | req.executor_public_key, 35 | ) 36 | .await 37 | .map_err(parse_api_error); 38 | 39 | match result { 40 | Ok(()) => ApiResponse { 41 | payload: UpdateContextApplicationResponse::new(), 42 | } 43 | .into_response(), 44 | Err(err) => err.into_response(), 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /crates/server/src/admin/handlers/identity.rs: -------------------------------------------------------------------------------- 1 | pub mod generate_context_identity; 2 | -------------------------------------------------------------------------------- /crates/server/src/admin/handlers/identity/generate_context_identity.rs: -------------------------------------------------------------------------------- 1 | use std::sync::Arc; 2 | 3 | use axum::response::IntoResponse; 4 | use axum::Extension; 5 | use calimero_server_primitives::admin::GenerateContextIdentityResponse; 6 | 7 | use crate::admin::service::ApiResponse; 8 | use crate::AdminState; 9 | 10 | pub async fn handler(Extension(state): Extension>) -> impl IntoResponse { 11 | let private_key = state.ctx_manager.new_private_key(); 12 | 13 | ApiResponse { 14 | payload: GenerateContextIdentityResponse::new(private_key.public_key(), private_key), 15 | } 16 | .into_response() 17 | } 18 | -------------------------------------------------------------------------------- /crates/server/src/admin/handlers/peers.rs: -------------------------------------------------------------------------------- 1 | use std::sync::Arc; 2 | 3 | use axum::response::IntoResponse; 4 | use axum::Extension; 5 | use calimero_server_primitives::admin::GetPeersCountResponse; 6 | 7 | use crate::admin::service::ApiResponse; 8 | use crate::AdminState; 9 | 10 | pub async fn get_peers_count_handler( 11 | Extension(state): Extension>, 12 | ) -> impl IntoResponse { 13 | let peer_count = state.ctx_manager.get_peers_count().await; 14 | 15 | ApiResponse { 16 | payload: GetPeersCountResponse::new(peer_count), 17 | } 18 | .into_response() 19 | } 20 | -------------------------------------------------------------------------------- /crates/server/src/admin/storage.rs: -------------------------------------------------------------------------------- 1 | pub mod client_keys; 2 | pub mod did; 3 | pub mod jwt_secret; 4 | pub mod jwt_token; 5 | pub mod root_key; 6 | pub mod ssl; 7 | -------------------------------------------------------------------------------- /crates/server/src/admin/utils.rs: -------------------------------------------------------------------------------- 1 | pub mod auth; 2 | #[cfg(test)] 3 | mod auth_tests; 4 | pub mod jwt; 5 | -------------------------------------------------------------------------------- /crates/server/src/admin/utils/auth_tests.rs: -------------------------------------------------------------------------------- 1 | use web3::signing::recover; 2 | 3 | use super::auth::*; 4 | 5 | #[test] 6 | fn test_recover() { 7 | let account = "0x04a4e95eeebe44a0f37b75f40957445d2d16a88c".to_string(); 8 | let message = "blabla"; 9 | let message_hash = eth_message(message); 10 | let signature ="0x15a88c2b8f3f3a0549c88dcbdba063de517ce55e68fdf2ad443f2c24f71904c740f212f0d74b94e271b9d549a8825894d0869b173709a5e798ec6997a1c72d5b1b".trim_start_matches("0x"); 11 | let signature = hex::decode(signature).unwrap(); 12 | println!("{} {:?} {:?}", account, message_hash, signature); 13 | let recovery_id = signature[64] as i32 - 27; 14 | let pubkey = recover(&message_hash, &signature[..64], recovery_id); 15 | assert!(pubkey.is_ok()); 16 | let pubkey = pubkey.unwrap(); 17 | let pubkey = format!("{:02X?}", pubkey); 18 | assert_eq!(account, pubkey) 19 | } 20 | -------------------------------------------------------------------------------- /crates/server/src/config.rs: -------------------------------------------------------------------------------- 1 | use core::net::{IpAddr, Ipv4Addr, Ipv6Addr}; 2 | 3 | use libp2p::identity::Keypair; 4 | use multiaddr::{Multiaddr, Protocol}; 5 | 6 | use crate::admin::service::AdminConfig; 7 | use crate::jsonrpc::JsonRpcConfig; 8 | use crate::ws::WsConfig; 9 | 10 | pub const DEFAULT_PORT: u16 = 2528; // (CHAT in T9) + 100 11 | pub const DEFAULT_ADDRS: [IpAddr; 2] = [ 12 | IpAddr::V4(Ipv4Addr::LOCALHOST), 13 | IpAddr::V6(Ipv6Addr::LOCALHOST), 14 | ]; 15 | 16 | #[derive(Debug)] 17 | #[non_exhaustive] 18 | pub struct ServerConfig { 19 | pub listen: Vec, 20 | 21 | pub identity: Keypair, 22 | 23 | #[cfg(feature = "admin")] 24 | pub admin: Option, 25 | 26 | #[cfg(feature = "jsonrpc")] 27 | pub jsonrpc: Option, 28 | 29 | #[cfg(feature = "websocket")] 30 | pub websocket: Option, 31 | } 32 | 33 | impl ServerConfig { 34 | #[must_use] 35 | pub const fn new( 36 | listen: Vec, 37 | identity: Keypair, 38 | admin: Option, 39 | jsonrpc: Option, 40 | websocket: Option, 41 | ) -> Self { 42 | Self { 43 | listen, 44 | identity, 45 | admin, 46 | jsonrpc, 47 | websocket, 48 | } 49 | } 50 | } 51 | 52 | #[must_use] 53 | pub fn default_addrs() -> Vec { 54 | DEFAULT_ADDRS 55 | .into_iter() 56 | .map(|addr| Multiaddr::from(addr).with(Protocol::Tcp(DEFAULT_PORT))) 57 | .collect() 58 | } 59 | -------------------------------------------------------------------------------- /crates/server/src/middleware.rs: -------------------------------------------------------------------------------- 1 | pub mod auth; 2 | pub mod dev_auth; 3 | #[cfg(feature = "host_layer")] 4 | pub mod host; 5 | pub mod jwt; 6 | -------------------------------------------------------------------------------- /crates/server/src/middleware/dev_auth.rs: -------------------------------------------------------------------------------- 1 | use std::sync::Arc; 2 | 3 | use axum::body::Body; 4 | use axum::http::{Request, StatusCode}; 5 | use axum::middleware::Next; 6 | use axum::response::Response; 7 | use axum::Extension; 8 | use chrono::{Duration, TimeZone, Utc}; 9 | 10 | use crate::AdminState; 11 | 12 | const TIMESTAMP_THRESHOLD: i64 = 5; 13 | 14 | pub async fn dev_mode_auth( 15 | state: Extension>, 16 | request: Request, 17 | next: Next, 18 | ) -> Result { 19 | let public_key = &state.keypair.public(); 20 | 21 | let signature = request 22 | .headers() 23 | .get("X-Signature") 24 | .and_then(|v| bs58::decode(v).into_vec().ok()) 25 | .ok_or(StatusCode::UNAUTHORIZED)?; 26 | 27 | let timestamp = request 28 | .headers() 29 | .get("X-Timestamp") 30 | .and_then(|v| v.to_str().ok()) 31 | .and_then(|v| v.parse::().ok()) 32 | .and_then(|t| Utc.timestamp_opt(t, 0).single()) 33 | .ok_or(StatusCode::UNAUTHORIZED)?; 34 | 35 | let now = Utc::now(); 36 | 37 | let time_diff = now.signed_duration_since(timestamp); 38 | if time_diff > Duration::seconds(TIMESTAMP_THRESHOLD) 39 | || time_diff < Duration::seconds(-TIMESTAMP_THRESHOLD) 40 | { 41 | return Err(StatusCode::UNAUTHORIZED); 42 | } 43 | 44 | let message = timestamp.timestamp().to_string(); 45 | 46 | if !public_key.verify(message.as_bytes(), &signature) { 47 | return Err(StatusCode::UNAUTHORIZED); 48 | } 49 | 50 | let response = next.run(request).await; 51 | 52 | Ok(response) 53 | } 54 | -------------------------------------------------------------------------------- /crates/server/src/tests/verifywalletsignatures/near.rs: -------------------------------------------------------------------------------- 1 | use super::*; 2 | 3 | #[test] 4 | fn test_verify_signature_valid() { 5 | let challenge = "89qdrkz1egXlJ2wwF1tcZpuFT0LXT4AHhnAnFvG3N/E="; 6 | let message = "helloworld"; 7 | let app = "me"; 8 | let curl = "http://127.0.0.1:2428/admin/confirm-wallet"; 9 | let signature_base64 = 10 | "rkBQLYN7xxe1oetSfktrqL5jgVsZWKNvKZJmoZLNh756KIUBseYIzK3Dt17O60aPMl6S17lDnIlLVLOLdi5OCw=="; 11 | let public_key = "ed25519:DxdDEdfg4sARk2YteEvp6KsqUGAgKyCZkYTqrboGWwiV"; 12 | 13 | assert!(verify_near_signature( 14 | challenge, 15 | message, 16 | app, 17 | curl, 18 | signature_base64, 19 | public_key 20 | )); 21 | } 22 | 23 | #[test] 24 | fn test_verify_signature_invalid() { 25 | let challenge = "89qdrkz1egXlJ2wwF1tcZpuFT0LXT4AHhnAnFvG3N/E="; 26 | let message = "helloworld"; 27 | let app = "me"; 28 | let curl = "http://127.0.0.1:2428/admin/confirm-wallet"; 29 | let signature_base64 = 30 | "rkBQLYN7xxe1oetSfktrqL5jgVsZWKNvKZJmoZLNh756KIUBseYIzK3Dt17O60aPMl6S17lDnIlLVsOLdi5OCw=="; 31 | let public_key = "ed25519:DxdDEdfg4sARk2YteEvp6KsqUGAgKyCZkYTqrboGWwiV"; 32 | 33 | assert!(!verify_near_signature( 34 | challenge, 35 | message, 36 | app, 37 | curl, 38 | signature_base64, 39 | public_key 40 | )); 41 | } 42 | -------------------------------------------------------------------------------- /crates/server/src/verifywalletsignatures.rs: -------------------------------------------------------------------------------- 1 | pub mod icp; 2 | pub mod near; 3 | pub mod starknet; 4 | -------------------------------------------------------------------------------- /crates/server/src/ws/subscribe.rs: -------------------------------------------------------------------------------- 1 | use std::sync::Arc; 2 | 3 | use calimero_server_primitives::ws::{SubscribeRequest, SubscribeResponse}; 4 | use calimero_server_primitives::Infallible; 5 | use eyre::Result as EyreResult; 6 | 7 | use crate::ws::{mount_method, ConnectionState, ServiceState}; 8 | 9 | mount_method!(SubscribeRequest-> Result, handle); 10 | 11 | async fn handle( 12 | request: SubscribeRequest, 13 | _state: Arc, 14 | connection_state: ConnectionState, 15 | ) -> EyreResult { 16 | let mut inner = connection_state.inner.write().await; 17 | request.context_ids.iter().for_each(|id| { 18 | let _ = inner.subscriptions.insert(*id); 19 | }); 20 | 21 | Ok(SubscribeResponse { 22 | context_ids: request.context_ids, 23 | }) 24 | } 25 | -------------------------------------------------------------------------------- /crates/server/src/ws/unsubscribe.rs: -------------------------------------------------------------------------------- 1 | use std::sync::Arc; 2 | 3 | use calimero_server_primitives::ws::{UnsubscribeRequest, UnsubscribeResponse}; 4 | use calimero_server_primitives::Infallible; 5 | use eyre::Result as EyreResult; 6 | 7 | use crate::ws::{mount_method, ConnectionState, ServiceState}; 8 | 9 | mount_method!(UnsubscribeRequest-> Result, handle); 10 | 11 | async fn handle( 12 | request: UnsubscribeRequest, 13 | _state: Arc, 14 | connection_state: ConnectionState, 15 | ) -> EyreResult { 16 | let mut inner = connection_state.inner.write().await; 17 | request.context_ids.iter().for_each(|id| { 18 | let _ = inner.subscriptions.remove(id); 19 | }); 20 | 21 | Ok(UnsubscribeResponse { 22 | context_ids: request.context_ids, 23 | }) 24 | } 25 | -------------------------------------------------------------------------------- /crates/storage-macros/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "calimero-storage-macros" 3 | version = "0.1.0" 4 | authors.workspace = true 5 | edition.workspace = true 6 | repository.workspace = true 7 | license.workspace = true 8 | 9 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 10 | 11 | [lib] 12 | proc-macro = true 13 | 14 | [dependencies] 15 | borsh.workspace = true 16 | quote.workspace = true 17 | syn.workspace = true 18 | 19 | [dev-dependencies] 20 | trybuild.workspace = true 21 | calimero-sdk.workspace = true 22 | calimero-storage.workspace = true 23 | 24 | [lints] 25 | workspace = true 26 | -------------------------------------------------------------------------------- /crates/storage/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "calimero-storage" 3 | version = "0.1.0" 4 | authors.workspace = true 5 | edition.workspace = true 6 | repository.workspace = true 7 | license.workspace = true 8 | 9 | [dependencies] 10 | borsh = { workspace = true, features = ["derive"] } 11 | eyre.workspace = true 12 | fixedstr = { workspace = true, features = ["flex-str", "serde", "std"] } 13 | hex.workspace = true 14 | indexmap.workspace = true 15 | serde = { workspace = true, features = ["derive"] } 16 | sha2.workspace = true 17 | thiserror.workspace = true 18 | 19 | calimero-sdk.workspace = true 20 | calimero-storage-macros.workspace = true 21 | 22 | [target.'cfg(not(target_arch = "wasm32"))'.dependencies] 23 | rand.workspace = true 24 | 25 | [dev-dependencies] 26 | claims.workspace = true 27 | hex.workspace = true 28 | velcro.workspace = true 29 | 30 | calimero-sdk.workspace = true 31 | 32 | [lints] 33 | workspace = true 34 | -------------------------------------------------------------------------------- /crates/storage/src/collections/error.rs: -------------------------------------------------------------------------------- 1 | //! Error types for storage operations. 2 | 3 | use serde::Serialize; 4 | use thiserror::Error; 5 | 6 | use crate::address::PathError; 7 | use crate::interface::StorageError; 8 | 9 | /// General error type for storage operations while interacting with complex collections. 10 | #[derive(Debug, Error)] 11 | #[non_exhaustive] 12 | pub enum StoreError { 13 | /// Error while interacting with storage. 14 | #[error(transparent)] 15 | StorageError(#[from] StorageError), 16 | /// Error while interacting with a path. 17 | #[error(transparent)] 18 | PathError(#[from] PathError), 19 | } 20 | 21 | impl Serialize for StoreError { 22 | fn serialize(&self, serializer: S) -> Result 23 | where 24 | S: serde::Serializer, 25 | { 26 | serializer.collect_str(self) 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /crates/storage/src/integration.rs: -------------------------------------------------------------------------------- 1 | //! Types used for integration with the runtime. 2 | 3 | use borsh::{BorshDeserialize, BorshSerialize}; 4 | 5 | use crate::interface::ComparisonData; 6 | 7 | /// Comparison data for synchronisation. 8 | #[derive(BorshDeserialize, BorshSerialize, Clone, Debug, Eq, Ord, PartialEq, PartialOrd)] 9 | #[expect(clippy::exhaustive_structs, reason = "Exhaustive")] 10 | pub struct Comparison { 11 | /// The serialised data of the entity. 12 | pub data: Option>, 13 | 14 | /// The comparison data for the entity. 15 | pub comparison_data: ComparisonData, 16 | } 17 | -------------------------------------------------------------------------------- /crates/store/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "calimero-store" 3 | version = "0.1.0" 4 | authors.workspace = true 5 | edition.workspace = true 6 | repository.workspace = true 7 | license.workspace = true 8 | 9 | [dependencies] 10 | borsh = { workspace = true, optional = true } 11 | camino.workspace = true 12 | eyre.workspace = true 13 | generic-array.workspace = true 14 | rocksdb.workspace = true 15 | serde = { workspace = true, optional = true } 16 | serde_json = { workspace = true, optional = true } 17 | strum = { workspace = true, features = ["derive"] } 18 | thiserror.workspace = true 19 | thunderdome.workspace = true 20 | 21 | calimero-primitives.workspace = true 22 | 23 | [dev-dependencies] 24 | tempdir.workspace = true 25 | 26 | [features] 27 | borsh = ["borsh/derive"] 28 | serde = ["dep:serde", "dep:serde_json"] 29 | datatypes = ["borsh", "calimero-primitives/borsh"] 30 | 31 | [lints] 32 | workspace = true 33 | -------------------------------------------------------------------------------- /crates/store/blobs/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "calimero-blobstore" 3 | version = "0.1.0" 4 | authors.workspace = true 5 | edition.workspace = true 6 | repository.workspace = true 7 | license.workspace = true 8 | 9 | [[example]] 10 | name = "blobstore" 11 | required-features = ["examples"] 12 | 13 | [dependencies] 14 | async-stream.workspace = true 15 | camino = { workspace = true, features = ["serde1"] } 16 | eyre.workspace = true 17 | futures-util = { workspace = true, features = ["io"] } 18 | serde = { workspace = true, features = ["derive"] } 19 | sha2.workspace = true 20 | thiserror.workspace = true 21 | tokio = { workspace = true, features = ["fs"] } 22 | 23 | calimero-primitives.workspace = true 24 | calimero-store = { workspace = true, features = ["datatypes"] } 25 | 26 | [dev-dependencies] 27 | tokio-util.workspace = true 28 | 29 | [features] 30 | examples = [ 31 | "tokio/io-std", 32 | "tokio/io-util", 33 | "tokio/macros", 34 | "tokio/rt-multi-thread", 35 | "tokio-util/io", 36 | "tokio-util/compat", 37 | ] 38 | 39 | [lints] 40 | workspace = true 41 | -------------------------------------------------------------------------------- /crates/store/blobs/src/config.rs: -------------------------------------------------------------------------------- 1 | use camino::Utf8PathBuf; 2 | use serde::{Deserialize, Serialize}; 3 | 4 | #[derive(Clone, Debug, Serialize, Deserialize)] 5 | #[non_exhaustive] 6 | pub struct BlobStoreConfig { 7 | pub path: Utf8PathBuf, 8 | } 9 | 10 | impl BlobStoreConfig { 11 | #[must_use] 12 | pub const fn new(path: Utf8PathBuf) -> Self { 13 | Self { path } 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /crates/store/src/config.rs: -------------------------------------------------------------------------------- 1 | use camino::Utf8PathBuf; 2 | 3 | #[derive(Debug)] 4 | #[non_exhaustive] 5 | pub struct StoreConfig { 6 | pub path: Utf8PathBuf, 7 | } 8 | 9 | impl StoreConfig { 10 | #[must_use] 11 | pub const fn new(path: Utf8PathBuf) -> Self { 12 | Self { path } 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /crates/store/src/db.rs: -------------------------------------------------------------------------------- 1 | use core::fmt::Debug; 2 | 3 | use eyre::Result as EyreResult; 4 | use strum::{AsRefStr, EnumIter}; 5 | 6 | use crate::config::StoreConfig; 7 | use crate::iter::Iter; 8 | use crate::slice::Slice; 9 | use crate::tx::Transaction; 10 | 11 | mod memory; 12 | mod rocksdb; 13 | 14 | pub use memory::InMemoryDB; 15 | pub use rocksdb::RocksDB; 16 | 17 | #[derive(AsRefStr, Clone, Copy, Debug, EnumIter, Eq, Ord, PartialEq, PartialOrd)] 18 | #[non_exhaustive] 19 | pub enum Column { 20 | Meta, 21 | Config, 22 | Identity, 23 | State, 24 | Blobs, 25 | Application, 26 | Generic, 27 | } 28 | 29 | pub trait Database<'a>: Debug + Send + Sync + 'static { 30 | fn open(config: &StoreConfig) -> EyreResult 31 | where 32 | Self: Sized; 33 | 34 | fn has(&self, col: Column, key: Slice<'_>) -> EyreResult; 35 | fn get(&self, col: Column, key: Slice<'_>) -> EyreResult>>; 36 | fn put(&self, col: Column, key: Slice<'a>, value: Slice<'a>) -> EyreResult<()>; 37 | fn delete(&self, col: Column, key: Slice<'_>) -> EyreResult<()>; 38 | 39 | // TODO: We should consider returning Iterator here. 40 | #[expect( 41 | clippy::iter_not_returning_iterator, 42 | reason = "TODO: This should be implemented" 43 | )] 44 | fn iter(&self, col: Column) -> EyreResult>; 45 | 46 | // todo! redesign this, each DB should return a transaction 47 | // todo! modelled similar to Iter - {put, delete, clear} 48 | fn apply(&self, tx: &Transaction<'a>) -> EyreResult<()>; 49 | } 50 | -------------------------------------------------------------------------------- /crates/store/src/key/component.rs: -------------------------------------------------------------------------------- 1 | use core::ops::Add; 2 | 3 | use generic_array::typenum::Sum; 4 | use generic_array::ArrayLength; 5 | 6 | pub trait KeyComponent { 7 | type LEN: ArrayLength; 8 | } 9 | 10 | pub trait KeyComponents { 11 | type LEN: ArrayLength; 12 | } 13 | 14 | impl KeyComponents for T { 15 | type LEN = T::LEN; 16 | } 17 | 18 | impl KeyComponents for (T,) { 19 | type LEN = T::LEN; 20 | } 21 | 22 | impl KeyComponents for (T, U) 23 | where 24 | T::LEN: Add, 25 | { 26 | type LEN = Sum; 27 | } 28 | -------------------------------------------------------------------------------- /crates/store/src/layer/read_only.rs: -------------------------------------------------------------------------------- 1 | use eyre::Result as EyreResult; 2 | 3 | use crate::iter::{Iter, Structured}; 4 | use crate::key::{AsKeyParts, FromKeyParts}; 5 | use crate::layer::{Layer, ReadLayer}; 6 | use crate::slice::Slice; 7 | 8 | #[derive(Debug)] 9 | pub struct ReadOnly<'base, L> { 10 | inner: &'base L, 11 | } 12 | 13 | impl<'base, L> ReadOnly<'base, L> 14 | where 15 | L: ReadLayer, 16 | { 17 | pub const fn new(layer: &'base L) -> Self { 18 | Self { inner: layer } 19 | } 20 | } 21 | 22 | impl Layer for ReadOnly<'_, L> { 23 | type Base = L; 24 | } 25 | 26 | impl ReadLayer for ReadOnly<'_, L> 27 | where 28 | L: ReadLayer, 29 | { 30 | fn has(&self, key: &K) -> EyreResult { 31 | self.inner.has(key) 32 | } 33 | 34 | fn get(&self, key: &K) -> EyreResult>> { 35 | self.inner.get(key) 36 | } 37 | 38 | fn iter(&self) -> EyreResult>> { 39 | self.inner.iter() 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /crates/store/src/lib.rs: -------------------------------------------------------------------------------- 1 | use std::sync::Arc; 2 | 3 | use eyre::Result as EyreResult; 4 | 5 | pub mod config; 6 | pub mod db; 7 | pub mod entry; 8 | mod handle; 9 | pub mod iter; 10 | pub mod key; 11 | pub mod layer; 12 | pub mod slice; 13 | mod tx; 14 | 15 | use config::StoreConfig; 16 | use db::Database; 17 | use handle::Handle; 18 | 19 | #[cfg(feature = "datatypes")] 20 | pub mod types; 21 | 22 | #[derive(Clone, Debug)] 23 | pub struct Store { 24 | db: Arc Database<'a>>, 25 | } 26 | 27 | impl Store { 28 | pub fn open Database<'a>>(config: &StoreConfig) -> EyreResult { 29 | let db = T::open(config)?; 30 | Ok(Self { db: Arc::new(db) }) 31 | } 32 | 33 | #[must_use] 34 | pub fn handle(&self) -> Handle { 35 | Handle::new(self.clone()) 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /crates/store/src/types.rs: -------------------------------------------------------------------------------- 1 | use crate::entry::{Codec, Entry}; 2 | use crate::key::AsKeyParts; 3 | 4 | mod application; 5 | mod blobs; 6 | mod context; 7 | mod generic; 8 | 9 | pub use application::ApplicationMeta; 10 | pub use blobs::BlobMeta; 11 | pub use context::{ContextConfig, ContextIdentity, ContextMeta, ContextState}; 12 | pub use generic::GenericData; 13 | 14 | pub trait PredefinedEntry: AsKeyParts { 15 | type Codec: for<'a> Codec<'a, Self::DataType<'a>>; 16 | type DataType<'a>; 17 | } 18 | 19 | impl Entry for T { 20 | type Key = T; 21 | type Codec = T::Codec; 22 | type DataType<'a> = T::DataType<'a>; 23 | 24 | fn key(&self) -> &Self::Key { 25 | self 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /crates/store/src/types/application.rs: -------------------------------------------------------------------------------- 1 | use borsh::{BorshDeserialize, BorshSerialize}; 2 | 3 | use crate::entry::Borsh; 4 | use crate::key::{ApplicationMeta as ApplicationMetaKey, BlobMeta as BlobMetaKey}; 5 | use crate::types::PredefinedEntry; 6 | 7 | #[derive(BorshDeserialize, BorshSerialize, Clone, Debug, Eq, PartialEq)] 8 | #[non_exhaustive] 9 | pub struct ApplicationMeta { 10 | // todo! impl proper entry reference count 11 | // pub refs: usize, 12 | pub blob: BlobMetaKey, 13 | pub size: u64, 14 | pub source: Box, 15 | pub metadata: Box<[u8]>, 16 | } 17 | 18 | impl ApplicationMeta { 19 | #[must_use] 20 | pub const fn new(blob: BlobMetaKey, size: u64, source: Box, metadata: Box<[u8]>) -> Self { 21 | Self { 22 | blob, 23 | size, 24 | source, 25 | metadata, 26 | } 27 | } 28 | } 29 | 30 | impl PredefinedEntry for ApplicationMetaKey { 31 | type Codec = Borsh; 32 | type DataType<'a> = ApplicationMeta; 33 | } 34 | -------------------------------------------------------------------------------- /crates/store/src/types/blobs.rs: -------------------------------------------------------------------------------- 1 | use borsh::{BorshDeserialize, BorshSerialize}; 2 | 3 | use crate::entry::Borsh; 4 | use crate::key::BlobMeta as BlobMetaKey; 5 | use crate::types::PredefinedEntry; 6 | 7 | #[derive(BorshDeserialize, BorshSerialize, Clone, Debug, Eq, PartialEq)] 8 | #[non_exhaustive] 9 | pub struct BlobMeta { 10 | // todo! impl proper entry reference count 11 | // pub refs: usize, 12 | pub size: u64, 13 | pub hash: [u8; 32], 14 | pub links: Box<[BlobMetaKey]>, 15 | } 16 | 17 | impl BlobMeta { 18 | #[must_use] 19 | pub const fn new(size: u64, hash: [u8; 32], links: Box<[BlobMetaKey]>) -> Self { 20 | Self { size, hash, links } 21 | } 22 | } 23 | 24 | impl PredefinedEntry for BlobMetaKey { 25 | type Codec = Borsh; 26 | type DataType<'a> = BlobMeta; 27 | } 28 | -------------------------------------------------------------------------------- /crates/store/src/types/generic.rs: -------------------------------------------------------------------------------- 1 | use crate::entry::Identity; 2 | use crate::key::Generic as GenericKey; 3 | use crate::slice::Slice; 4 | use crate::types::PredefinedEntry; 5 | 6 | #[derive(Clone, Debug, Eq, PartialEq)] 7 | pub struct GenericData<'a> { 8 | value: Slice<'a>, 9 | } 10 | 11 | impl PredefinedEntry for GenericKey { 12 | type Codec = Identity; 13 | type DataType<'a> = GenericData<'a>; 14 | } 15 | 16 | impl<'a> From> for GenericData<'a> { 17 | fn from(value: Slice<'a>) -> Self { 18 | Self { value } 19 | } 20 | } 21 | 22 | impl AsRef<[u8]> for GenericData<'_> { 23 | fn as_ref(&self) -> &[u8] { 24 | self.value.as_ref() 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: '3.8' 2 | 3 | services: 4 | app_node_init: 5 | image: merod:latest 6 | command: > 7 | --node-name node1 --home /data init --server-host 0.0.0.0 --server-port 2428 --swarm-port 2528 8 | volumes: 9 | - ./data:/data 10 | 11 | app_node_run: 12 | image: merod:latest 13 | ports: 14 | - '2428:2428' 15 | - '2528:2528' 16 | command: > 17 | --node-name node1 --home /data run 18 | volumes: 19 | - ./data:/data 20 | - ./certs:/certs 21 | depends_on: 22 | - app_node_init 23 | -------------------------------------------------------------------------------- /e2e-tests/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "e2e-tests" 3 | version = "0.1.0" 4 | authors.workspace = true 5 | edition.workspace = true 6 | repository.workspace = true 7 | license.workspace = true 8 | 9 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 10 | 11 | [dependencies] 12 | camino = { workspace = true, features = ["serde1"] } 13 | clap = { workspace = true, features = ["env", "derive"] } 14 | const_format.workspace = true 15 | eyre.workspace = true 16 | near-workspaces.workspace = true 17 | nix = { version = "0.29.0", features = ["signal"] } 18 | rand.workspace = true 19 | serde = { workspace = true, features = ["derive"] } 20 | serde_json.workspace = true 21 | tokio = { workspace = true, features = ["fs", "io-util", "macros", "process", "rt", "rt-multi-thread", "time"] } 22 | url = { workspace = true } 23 | 24 | [lints] 25 | workspace = true 26 | -------------------------------------------------------------------------------- /e2e-tests/README.md: -------------------------------------------------------------------------------- 1 | # E2e tests 2 | 3 | Binary crate which runs e2e tests for the merod node. 4 | 5 | ## Usage 6 | 7 | First build apps, contracts, and mero binaries. After that run the e2e tests. 8 | 9 | Example of running the e2e tests: 10 | 11 | ```bash 12 | ./apps/kv-store/build.sh 13 | 14 | ./contracts/context-config/build.sh 15 | ./contracts/proxy-lib/build.sh 16 | 17 | cargo build -p merod 18 | cargo build -p meroctl 19 | 20 | export NO_COLOR=1 # Disable color output for merod logs 21 | cargo run -p e2e-tests -- --input-dir ./e2e-tests/config --output-dir ./e2e-tests/corpus --merod-binary ./target/debug/merod --meroctl-binary ./target/debug/meroctl 22 | ``` 23 | 24 | Useful env vars for debugging: 25 | 26 | - `RUST_LOG=debug` - enable debug logs 27 | - `RUST_LOG=near_jsonrpc_client=debug` - or more specific logs 28 | - `NEAR_ENABLE_SANDBOX_LOG=1` - enable near sandbox logs 29 | - `NO_COLOR=1` - disable color output 30 | -------------------------------------------------------------------------------- /e2e-tests/config/config.json: -------------------------------------------------------------------------------- 1 | { 2 | "network": { 3 | "nodeCount": 2, 4 | "swarmHost": "0.0.0.0", 5 | "startSwarmPort": 2427, 6 | "startServerPort": 2527 7 | }, 8 | "merod": { 9 | "args": [] 10 | }, 11 | "protocolSandboxes": [ 12 | { 13 | "protocol": "near", 14 | "config": { 15 | "contextConfigContract": "./contracts/near/context-config/res/calimero_context_config_near.wasm", 16 | "proxyLibContract": "./contracts/near/context-proxy/res/calimero_context_proxy_near.wasm" 17 | } 18 | }, 19 | { 20 | "protocol": "icp", 21 | "config": { 22 | "contextConfigContractId": "bkyz2-fmaaa-aaaaa-qaaaq-cai", 23 | "rpcUrl": "http://127.0.0.1:4943", 24 | "accountId": "fph2z-lxdui-xq3o6-6kuqy-rgkwi-hq7us-gkwlq-gxfgs-irrcq-hnm4e-6qe", 25 | "publicKey": "e3a22f0dbbde552188995641e1fa48cab2e06b94d24462281dace13d02", 26 | "secretKey": "c9a8e56920efd1c7b6694dce6ce871b661ae3922d5045d4a9f04e131eaa34164" 27 | } 28 | } 29 | ] 30 | } 31 | -------------------------------------------------------------------------------- /e2e-tests/config/scenarios/kv-store/test.json: -------------------------------------------------------------------------------- 1 | { 2 | "steps": [ 3 | { 4 | "applicationInstall": { 5 | "application": { "localFile": "./apps/kv-store/res/kv_store.wasm" }, 6 | "target": "allMembers" 7 | } 8 | }, 9 | { 10 | "contextCreate": null 11 | }, 12 | { 13 | "call": { 14 | "methodName": "set", 15 | "argsJson": { "key": "foo", "value": "bar" }, 16 | "expectedResultJson": null, 17 | "target": "inviter" 18 | } 19 | }, 20 | { 21 | "call": { 22 | "methodName": "get", 23 | "argsJson": { "key": "foo" }, 24 | "expectedResultJson": "bar", 25 | "target": "inviter" 26 | } 27 | }, 28 | { 29 | "contextInviteJoin": null 30 | }, 31 | { 32 | "call": { 33 | "methodName": "get", 34 | "argsJson": { "key": "foo" }, 35 | "expectedResultJson": "bar", 36 | "target": "allMembers" 37 | } 38 | } 39 | ] 40 | } 41 | -------------------------------------------------------------------------------- /e2e-tests/src/config.rs: -------------------------------------------------------------------------------- 1 | use serde::{Deserialize, Serialize}; 2 | 3 | use crate::protocol::icp::IcpProtocolConfig; 4 | use crate::protocol::near::NearProtocolConfig; 5 | 6 | #[derive(Clone, Debug, Deserialize, Serialize)] 7 | #[serde(rename_all = "camelCase")] 8 | pub struct Config { 9 | pub network: Network, 10 | pub merod: MerodConfig, 11 | pub protocol_sandboxes: Box<[ProtocolSandboxConfig]>, 12 | } 13 | 14 | #[derive(Clone, Debug, Deserialize, Serialize)] 15 | #[serde(rename_all = "camelCase")] 16 | pub struct Network { 17 | pub node_count: u32, 18 | pub swarm_host: String, 19 | pub start_swarm_port: u32, 20 | pub start_server_port: u32, 21 | } 22 | 23 | #[derive(Clone, Debug, Deserialize, Serialize)] 24 | #[serde(rename_all = "camelCase")] 25 | pub struct MerodConfig { 26 | pub args: Box<[String]>, 27 | } 28 | 29 | #[derive(Clone, Debug, Deserialize, Serialize)] 30 | #[serde(tag = "protocol", content = "config", rename_all = "camelCase")] 31 | pub enum ProtocolSandboxConfig { 32 | Near(NearProtocolConfig), 33 | Icp(IcpProtocolConfig), 34 | } 35 | -------------------------------------------------------------------------------- /e2e-tests/src/protocol.rs: -------------------------------------------------------------------------------- 1 | use eyre::Result as EyreResult; 2 | use icp::IcpSandboxEnvironment; 3 | use near::NearSandboxEnvironment; 4 | 5 | pub mod icp; 6 | pub mod near; 7 | 8 | pub enum ProtocolSandboxEnvironment { 9 | Near(NearSandboxEnvironment), 10 | Icp(IcpSandboxEnvironment), 11 | } 12 | 13 | impl ProtocolSandboxEnvironment { 14 | pub async fn node_args(&self, node_name: &str) -> EyreResult> { 15 | match self { 16 | Self::Near(env) => env.node_args(node_name).await, 17 | Self::Icp(env) => Ok(env.node_args()), 18 | } 19 | } 20 | 21 | pub fn name(&self) -> &str { 22 | match self { 23 | Self::Near(_) => "near", 24 | Self::Icp(_) => "icp", 25 | } 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /e2e-tests/src/steps/context_create.rs: -------------------------------------------------------------------------------- 1 | use eyre::{bail, Result as EyreResult}; 2 | use serde::{Deserialize, Serialize}; 3 | 4 | use crate::driver::{Test, TestContext}; 5 | 6 | #[derive(Clone, Debug, Deserialize, Serialize)] 7 | #[serde(rename_all = "camelCase")] 8 | pub struct ContextCreateStep; 9 | 10 | impl Test for ContextCreateStep { 11 | fn display_name(&self) -> String { 12 | "ctx create".to_owned() 13 | } 14 | 15 | async fn run_assert(&self, ctx: &mut TestContext<'_>) -> EyreResult<()> { 16 | let Some(ref application_id) = ctx.application_id else { 17 | bail!("Application ID is required for ContextCreateStep"); 18 | }; 19 | 20 | let (context_id, member_public_key) = ctx 21 | .meroctl 22 | .context_create(&ctx.inviter, application_id, &ctx.protocol_name) 23 | .await?; 24 | 25 | ctx.context_id = Some(context_id); 26 | ctx.inviter_public_key = Some(member_public_key); 27 | 28 | ctx.output_writer.write_str(&format!( 29 | "Report: Created context on '{}' node", 30 | &ctx.inviter 31 | )); 32 | 33 | Ok(()) 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /node-ui/.env: -------------------------------------------------------------------------------- 1 | VITE_NEAR_ENVIRONMENT=testnet 2 | VITE_NODE_URL=http://localhost:2428 3 | -------------------------------------------------------------------------------- /node-ui/.eslint.config.mjs: -------------------------------------------------------------------------------- 1 | import { defineConfig } from 'eslint-define-config'; 2 | 3 | export default defineConfig({ 4 | root: true, 5 | env: { 6 | browser: true, 7 | es2020: true, 8 | }, 9 | extends: [ 10 | 'eslint:recommended', 11 | 'plugin:@typescript-eslint/recommended', 12 | 'plugin:react/recommended', 13 | 'plugin:react/jsx-runtime', 14 | 'plugin:react-hooks/recommended', 15 | 'react-app', 16 | 'plugin:prettier/recommended', 17 | ], 18 | ignorePatterns: ['build', '.eslint.config.mjs'], 19 | parser: '@typescript-eslint/parser', 20 | parserOptions: { 21 | ecmaVersion: 'latest', 22 | sourceType: 'module', 23 | project: ['./tsconfig.json'], 24 | }, 25 | settings: { 26 | react: { 27 | version: '18.2', 28 | }, 29 | }, 30 | plugins: ['@typescript-eslint', 'react-refresh'], 31 | rules: { 32 | 'react/jsx-no-target-blank': 'off', 33 | 'react/jsx-uses-react': 'error', 34 | 'react/jsx-uses-vars': 'error', 35 | 'react-refresh/only-export-components': [ 36 | 'warn', 37 | { allowConstantExport: true }, 38 | ], 39 | }, 40 | }); 41 | -------------------------------------------------------------------------------- /node-ui/.eslintignore: -------------------------------------------------------------------------------- 1 | # Ignore artifacts: 2 | build 3 | coverage 4 | node_modules 5 | -------------------------------------------------------------------------------- /node-ui/.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | pnpm-debug.log* 8 | lerna-debug.log* 9 | 10 | node_modules 11 | dist 12 | dist-ssr 13 | *.local 14 | 15 | # Editor directories and files 16 | .vscode/* 17 | !.vscode/extensions.json 18 | .idea 19 | .DS_Store 20 | *.suo 21 | *.ntvs* 22 | *.njsproj 23 | *.sln 24 | *.sw? 25 | -------------------------------------------------------------------------------- /node-ui/.prettierignore: -------------------------------------------------------------------------------- 1 | # Ignore artifacts: 2 | build 3 | coverage 4 | node_modules 5 | -------------------------------------------------------------------------------- /node-ui/build/assets/favicon-CrZyDFVy.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/smartMinion221/stellarProject/64378f9f32626246fc2c975ce9fd813f1a6fa266/node-ui/build/assets/favicon-CrZyDFVy.ico -------------------------------------------------------------------------------- /node-ui/custom.d.ts: -------------------------------------------------------------------------------- 1 | declare module '*.svg' { 2 | const content: React.FunctionComponent>; 3 | export default content; 4 | } 5 | -------------------------------------------------------------------------------- /node-ui/prettier.config.cjs: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | semi: true, 3 | singleQuote: true, 4 | useTabs: false, 5 | tabWidth: 2, 6 | trailingComma: 'all', 7 | }; 8 | -------------------------------------------------------------------------------- /node-ui/src/api/index.ts: -------------------------------------------------------------------------------- 1 | import axios from 'axios'; 2 | import { AxiosHttpClient, HttpClient } from './httpClient'; 3 | import { NodeApi } from './nodeApi'; 4 | import { NodeDataSource } from './dataSource/NodeDataSource'; 5 | 6 | interface IApiClient { 7 | node(): NodeApi; 8 | } 9 | 10 | class ApiClient implements IApiClient { 11 | private nodeApi: NodeApi; 12 | 13 | constructor(httpClient: HttpClient) { 14 | this.nodeApi = new NodeDataSource(httpClient); 15 | } 16 | 17 | node() { 18 | return this.nodeApi; 19 | } 20 | } 21 | 22 | const apiClient = (showServerDownPopup: () => void): ApiClient => { 23 | const httpClient = new AxiosHttpClient(axios, showServerDownPopup); 24 | return new ApiClient(httpClient); 25 | }; 26 | 27 | export default apiClient; 28 | -------------------------------------------------------------------------------- /node-ui/src/api/response.ts: -------------------------------------------------------------------------------- 1 | export type ResponseData = 2 | | { 3 | data: D; 4 | error?: null; 5 | } 6 | | { 7 | data?: null; 8 | error: ErrorResponse; 9 | }; 10 | 11 | export type ErrorResponse = { 12 | code?: number; 13 | message: string; 14 | }; 15 | 16 | export interface SuccessResponse { 17 | success: boolean; 18 | } 19 | 20 | export type ApiResponse = Promise>; 21 | -------------------------------------------------------------------------------- /node-ui/src/assets/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/smartMinion221/stellarProject/64378f9f32626246fc2c975ce9fd813f1a6fa266/node-ui/src/assets/favicon.ico -------------------------------------------------------------------------------- /node-ui/src/assets/login-icon.svg: -------------------------------------------------------------------------------- 1 | 2 | 12 | 22 | 32 | 42 | -------------------------------------------------------------------------------- /node-ui/src/assets/near-gif.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/smartMinion221/stellarProject/64378f9f32626246fc2c975ce9fd813f1a6fa266/node-ui/src/assets/near-gif.gif -------------------------------------------------------------------------------- /node-ui/src/assets/near-icon.svg: -------------------------------------------------------------------------------- 1 | 8 | 12 | -------------------------------------------------------------------------------- /node-ui/src/auth/ed25519.ts: -------------------------------------------------------------------------------- 1 | import { generateKeyPair } from '@libp2p/crypto/keys'; 2 | import bs58 from 'bs58'; 3 | 4 | import { PrivateKey, Ed25519 } from '@libp2p/interface'; 5 | import { getStorageClientKey, setStorageClientKey } from './storage'; 6 | import { ClientKey } from './types'; 7 | 8 | export async function generatePrivateKey(): Promise { 9 | return await generateKeyPair(Ed25519); 10 | } 11 | 12 | export async function getOrCreateKeypair(): Promise { 13 | return getStorageClientKey() ?? createAndStoreClientKey(); 14 | } 15 | 16 | async function createAndStoreClientKey() { 17 | const privateKey = await generatePrivateKey(); 18 | const clientKey: ClientKey = { 19 | privateKey: bs58.encode(privateKey.bytes), 20 | publicKey: bs58.encode(privateKey.public.bytes), 21 | }; 22 | setStorageClientKey(clientKey); 23 | 24 | return clientKey; 25 | } 26 | -------------------------------------------------------------------------------- /node-ui/src/auth/types.ts: -------------------------------------------------------------------------------- 1 | export interface ClientKey { 2 | privateKey: string; 3 | publicKey: string; 4 | } 5 | -------------------------------------------------------------------------------- /node-ui/src/components/applications/ApplicationsContent.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import styled from 'styled-components'; 3 | import { ButtonLight } from '../common/ButtonLight'; 4 | 5 | export const ContentLayout = styled.div` 6 | padding: 42px 26px 26px 26px; 7 | display: flex; 8 | flex: 1; 9 | .content-card { 10 | background-color: #353540; 11 | border-radius: 4px; 12 | padding: 30px 26px 26px 30px; 13 | width: 100%; 14 | } 15 | 16 | .page-title { 17 | color: #fff; 18 | font-size: 24px; 19 | font-weight: semi-bold; 20 | } 21 | .card-header { 22 | display: flex; 23 | justify-content: space-between; 24 | align-items: center; 25 | } 26 | `; 27 | 28 | interface ApplicationsContentProps { 29 | children: React.ReactNode; 30 | redirectAppUpload: () => void; 31 | } 32 | 33 | export function ApplicationsContent(props: ApplicationsContentProps) { 34 | return ( 35 | 36 |
37 |
38 |
Applications
39 | 43 |
44 | {props.children} 45 |
46 |
47 | ); 48 | } 49 | -------------------------------------------------------------------------------- /node-ui/src/components/common/ButtonLight.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import styled from 'styled-components'; 3 | 4 | const Button = styled.div` 5 | display: inline-flex; 6 | -webkit-box-align: center; 7 | align-items: center; 8 | -webkit-box-pack: center; 9 | justify-content: center; 10 | position: relative; 11 | box-sizing: border-box; 12 | -webkit-tap-highlight-color: transparent; 13 | outline: 0px; 14 | border: 0px; 15 | margin: 0px; 16 | cursor: pointer; 17 | user-select: none; 18 | vertical-align: middle; 19 | appearance: none; 20 | text-decoration: none; 21 | transition: 22 | background-color 250ms cubic-bezier(0.4, 0, 0.2, 1) 0ms, 23 | box-shadow 250ms cubic-bezier(0.4, 0, 0.2, 1) 0ms, 24 | border-color 250ms cubic-bezier(0.4, 0, 0.2, 1) 0ms, 25 | color 250ms cubic-bezier(0.4, 0, 0.2, 1) 0ms; 26 | background-color: rgb(255, 132, 45); 27 | box-shadow: 28 | rgba(0, 0, 0, 0.2) 0px 3px 1px -2px, 29 | rgba(0, 0, 0, 0.14) 0px 2px 2px 0px, 30 | rgba(0, 0, 0, 0.12) 0px 1px 5px 0px; 31 | min-width: 0px; 32 | border-radius: 4px; 33 | white-space: nowrap; 34 | color: rgb(23, 23, 29); 35 | font-weight: 400; 36 | font-size: 14px; 37 | padding-left: 10px; 38 | padding-right: 10px; 39 | letter-spacing: 0px; 40 | 41 | &:hover { 42 | background-color: #ac5221; 43 | } 44 | `; 45 | 46 | interface ButtonLightProps { 47 | text: string; 48 | onClick: () => void; 49 | } 50 | 51 | export function ButtonLight({ text, onClick }: ButtonLightProps) { 52 | return ( 53 | 56 | ); 57 | } 58 | -------------------------------------------------------------------------------- /node-ui/src/components/common/LoaderSpinner.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import styled from 'styled-components'; 3 | 4 | const Wrapper = styled.div` 5 | display: flex; 6 | justify-content: center; 7 | align-items: center; 8 | 9 | .lds-ring, 10 | .lds-ring div { 11 | box-sizing: border-box; 12 | } 13 | .lds-ring { 14 | display: inline-block; 15 | position: relative; 16 | width: 20px; 17 | height: 20px; 18 | } 19 | .lds-ring div { 20 | box-sizing: border-box; 21 | display: block; 22 | position: absolute; 23 | width: 20px; 24 | height: 20px; 25 | border: 8px solid rgb(255, 255, 255, 0.7); 26 | border-radius: 50%; 27 | animation: lds-ring 1.2s cubic-bezier(0.5, 0, 0.5, 1) infinite; 28 | border-color: rgb(255, 255, 255, 0.7) transparent transparent transparent; 29 | } 30 | .lds-ring div:nth-child(1) { 31 | animation-delay: -0.45s; 32 | } 33 | .lds-ring div:nth-child(2) { 34 | animation-delay: -0.3s; 35 | } 36 | .lds-ring div:nth-child(3) { 37 | animation-delay: -0.15s; 38 | } 39 | @keyframes lds-ring { 40 | 0% { 41 | transform: rotate(0deg); 42 | } 43 | 100% { 44 | transform: rotate(360deg); 45 | } 46 | } 47 | `; 48 | 49 | export default function LoaderSpinner() { 50 | return ( 51 | 52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 | ); 60 | } 61 | -------------------------------------------------------------------------------- /node-ui/src/components/common/Loading.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { styled } from 'styled-components'; 3 | 4 | interface LoaderComponentProps { 5 | $loaderColor: string; 6 | $loaderSize: string; 7 | $borderSize: string; 8 | } 9 | 10 | const Loader = styled.span` 11 | width: ${(props) => props.$loaderSize}; 12 | height: ${(props) => props.$loaderSize}; 13 | border: ${(props) => props.$borderSize} solid #FFF; 14 | border-bottom-color: ${(props) => props.$loaderColor}; 15 | border-radius: 50%; 16 | display: inline-block; 17 | box-sizing: border-box; 18 | animation: rotation 1s linear infinite; 19 | } 20 | 21 | @keyframes rotation { 22 | 0% { 23 | transform: rotate(0deg); 24 | } 25 | 100% { 26 | transform: rotate(360deg); 27 | } 28 | `; 29 | 30 | interface LoadingProps { 31 | loaderColor?: string; 32 | loaderSize?: string; 33 | borderSize?: string; 34 | } 35 | 36 | export default function Loading({ 37 | loaderColor = '#111', 38 | loaderSize = '20px', 39 | borderSize = '2px', 40 | }: LoadingProps) { 41 | return ( 42 | 47 | ); 48 | } 49 | -------------------------------------------------------------------------------- /node-ui/src/components/common/PageContentWrapper.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import styled from 'styled-components'; 3 | 4 | const PageWrapper = styled.div<{ $isOverflow: boolean }>` 5 | padding: 4.705rem 2rem 2rem; 6 | ${(props) => (props.$isOverflow ? 'overflow-y: scroll;' : 'display: flex;')} 7 | flex: 1; 8 | justify-content: center; 9 | align-items: center; 10 | `; 11 | 12 | interface PageContentWrapperProps { 13 | children: React.ReactNode; 14 | isOverflow?: boolean; 15 | } 16 | 17 | export default function PageContentWrapper({ 18 | children, 19 | isOverflow = false, 20 | }: PageContentWrapperProps) { 21 | return {children}; 22 | } 23 | -------------------------------------------------------------------------------- /node-ui/src/components/context/contextDetails/UserRowItem.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import styled from 'styled-components'; 3 | import { User } from '../../../api/dataSource/NodeDataSource'; 4 | 5 | interface UserRowItemProps { 6 | $hasBorders: boolean; 7 | } 8 | 9 | const RowItem = styled.div` 10 | display: flex; 11 | width: 100%; 12 | align-items: center; 13 | gap: 1px; 14 | font-size: 0.875rem; 15 | font-weight: 400; 16 | line-height: 1.25rem; 17 | text-align: left; 18 | padding-right: 1.5rem; 19 | padding-left: 1.5rem; 20 | ${(props) => 21 | props.$hasBorders 22 | ? ` 23 | border-top: 1px solid #23262D; 24 | border-bottom: 1px solid #23262D; 25 | ` 26 | : ` 27 | border-top: 1px solid #23262D; 28 | `} 29 | 30 | .row-item { 31 | padding: 0.75rem 0rem; 32 | height: 2.5rem; 33 | width: 50%; 34 | color: #fff; 35 | } 36 | `; 37 | 38 | export default function userRowItem(item: User, id: number, count: number) { 39 | return ( 40 | 41 |
{item.userId}
42 |
{item.joined_at}
43 |
44 | ); 45 | } 46 | -------------------------------------------------------------------------------- /node-ui/src/components/footer/Footer.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import styled from 'styled-components'; 3 | import translations from '../../constants/en.global.json'; 4 | 5 | const FooterWrapper = styled.div` 6 | display: flex; 7 | justify-content: center; 8 | align-items: center; 9 | position: absolute; 10 | bottom: 0; 11 | width: 100%; 12 | padding-bottom: 1rem; 13 | 14 | .footer-text { 15 | margin: 0px 0px 0.35em; 16 | font-weight: 400; 17 | font-size: 14px; 18 | line-height: 21px; 19 | letter-spacing: 0px; 20 | text-transform: none; 21 | color: rgba(255, 255, 255, 0.7); 22 | text-decoration: none; 23 | } 24 | `; 25 | 26 | export function Footer() { 27 | const t = translations.footer; 28 | return ( 29 | 30 | 36 | {t.title} 37 | 38 | 39 | ); 40 | } 41 | -------------------------------------------------------------------------------- /node-ui/src/components/layout/FlexLayout.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import styled from 'styled-components'; 3 | 4 | const FlexWrapper = styled.div` 5 | display: flex; 6 | height: 100vh; 7 | background-color: #111111; 8 | `; 9 | 10 | interface FlexLayoutProps { 11 | children: React.ReactNode; 12 | } 13 | 14 | export function FlexLayout({ children }: FlexLayoutProps) { 15 | return {children}; 16 | } 17 | -------------------------------------------------------------------------------- /node-ui/src/components/login/applicationLogin/ListItem.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import styled from 'styled-components'; 3 | 4 | interface RowItemProps { 5 | $hasBorders: boolean; 6 | } 7 | 8 | const RowItem = styled.div` 9 | display: flex; 10 | width: 100%; 11 | align-items: center; 12 | gap: 1px; 13 | font-size: 0.875rem; 14 | text-align: left; 15 | padding-right: 1.5rem; 16 | border-top: 1px solid #23262d; 17 | ${(props) => props.$hasBorders && `border-bottom: 1px solid #23262D;`} 18 | 19 | .row-item { 20 | padding: 0.75rem 0rem; 21 | display: flex; 22 | align-items: center; 23 | width: 25%; 24 | } 25 | .name { 26 | text-align: left; 27 | &:hover { 28 | color: #4cfafc; 29 | cursor: pointer; 30 | } 31 | } 32 | `; 33 | 34 | interface ListItemProps { 35 | item: string; 36 | id: number; 37 | count: number; 38 | onRowItemClick?: (id: string) => void; 39 | } 40 | 41 | export default function ListItem({ 42 | item, 43 | id, 44 | count, 45 | onRowItemClick, 46 | }: ListItemProps) { 47 | return ( 48 | 49 |
onRowItemClick && onRowItemClick(item)} 52 | > 53 | {item} 54 |
55 |
56 | ); 57 | } 58 | -------------------------------------------------------------------------------- /node-ui/src/components/metamask/MetamaskRoute.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | import { MetaMaskUIProvider } from '@metamask/sdk-react-ui'; 4 | import { Outlet } from 'react-router-dom'; 5 | import translations from '../../constants/en.global.json'; 6 | 7 | export default function MetamaskRoute() { 8 | const t = translations.useMetamask; 9 | 10 | return ( 11 | 19 | 20 | 21 | ); 22 | } 23 | -------------------------------------------------------------------------------- /node-ui/src/components/near/NearRoute.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | import { WalletSelectorContextProvider } from '../../context/WalletSelectorContext'; 4 | import { getNearEnvironment } from '../../utils/node'; 5 | import { Outlet } from 'react-router-dom'; 6 | 7 | export default function NearRoute() { 8 | return ( 9 | 10 | 11 | 12 | ); 13 | } 14 | -------------------------------------------------------------------------------- /node-ui/src/components/protectedRoutes/ProtectedRoute.tsx: -------------------------------------------------------------------------------- 1 | import React, { useEffect } from 'react'; 2 | import { useNavigate, Outlet, useLocation } from 'react-router-dom'; 3 | import { isNodeAuthorized, getClientKey } from '../../utils/storage'; 4 | import { getPathname } from '../../utils/protectedRoute'; 5 | 6 | export default function ProtectedRoute() { 7 | const { search } = useLocation(); 8 | const navigate = useNavigate(); 9 | const clientKey = getClientKey(); 10 | const isAuthorized = isNodeAuthorized(); 11 | const pathname = getPathname(); 12 | 13 | useEffect(() => { 14 | const isAuthPath = pathname.startsWith('/auth'); 15 | if (isAuthPath) { 16 | if (isAuthorized && clientKey) { 17 | //navigate to home page after auth is successfull 18 | navigate(`/identity${search}`); 19 | } 20 | } else { 21 | if (!(isAuthorized && clientKey)) { 22 | //show setup if not authorized 23 | navigate(`/${search ? `${search}` : '/'}`); 24 | } 25 | } 26 | // eslint-disable-next-line react-hooks/exhaustive-deps 27 | }, [isAuthorized, clientKey]); 28 | 29 | return ; 30 | } 31 | -------------------------------------------------------------------------------- /node-ui/src/components/publishApplication/ConnectWalletAccountCard.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import styled from 'styled-components'; 3 | import Button from '../common/Button'; 4 | import translations from '../../constants/en.global.json'; 5 | 6 | const CardWrapper = styled.div` 7 | display: flex; 8 | flex-direction: column; 9 | gap: 1rem; 10 | padding-left: 1rem; 11 | 12 | .title { 13 | color: #fff; 14 | font-size: 1rem; 15 | font-weight: 500; 16 | line-height: 1.25rem; 17 | text-align: left; 18 | } 19 | 20 | .subtitle { 21 | color: rgb(255, 255, 255, 0.4); 22 | font-size: 0.625rem; 23 | font-weight: 500; 24 | line-height: 0.75rem; 25 | text-align: left; 26 | } 27 | `; 28 | 29 | interface ConnectWalletAccountCardProps { 30 | onClick: () => void; 31 | deployerAccount: string | undefined; 32 | } 33 | 34 | export function ConnectWalletAccountCard({ 35 | onClick, 36 | deployerAccount, 37 | }: ConnectWalletAccountCardProps) { 38 | const t = translations.applicationsPage.publishApplication; 39 | return ( 40 | 41 |
{t.connectAccountTitle}
42 |
{t.connectAccountSubtitle}
43 |