diff --git a/img/architecture.png b/img/architecture.png new file mode 100644 index 0000000..f8927f3 Binary files /dev/null and b/img/architecture.png differ diff --git a/readme.md b/readme.md index 9ba2f00..406a041 100644 --- a/readme.md +++ b/readme.md @@ -1,51 +1,46 @@ # Karst -Karst is a web3 social graph on Starknet. it aims to build a social infrastructure for the starknet ecosystem. - -## TODOS - -- [ ] Implement `create profiles contract` functionality using `erc6551` - - [x] Implement `create profile` functionality - - [x] Implement `setProfileMetadataURI` functionality - - [x] Write test for `createProfile` and related `profile` functions -- [ ] implement `Publications` contract -- [ ] Make contract upgradable, preferably uups. - - [ ] implement `post` functionality - - [ ] Implement `like` functionality - - [ ] Implement `comment` functionality - - [ ] Implement `mirror` functionality - - [ ] Implement `quote` functionality - - [ ] implement `tipPost` functionality - - [ ] implement `follow` functionality from followNFT -- [ ] Implement `FollowNFT` contract - - [ ] implement `unwrap` functionality - - [ ] Implement `approveFollow` functionality - - [ ] Implement `removeFollower` functionality - - [ ] Implement `wrap` functionality - - [ ] Implement `follow` functionality - - [ ] Implement `unfollow` functionality - - [ ] Implement `getOriginalFollowTimestamp` functionality - - [ ] Implement `getFollowTimestamp` functionality - - [ ] Implement `getProfileIdAllowedToRecover` functionality - - [ ] Implement `getFollowData` functionality - - [ ] Implement `getFollowApproved` functionality - - [ ] Implement `getFollowerCount` functionality -- Implement `addDelegate` functionality -- [ ] Implement indexing of publish contract - - [ ] indexing shall be done with [arweave](https://www.arweave.org/) - - [ ] index all events emitted by the publications contract -- [ ] set up api endpoints to query the indexer -- [ ] not important at the moment - - [ ] create a custom explorer for querying the content layer - -## Remarks - -our implementation may defer from lens by they both achieve the same goal -link to [Lens protocol](https://polygonscan.com/address/0x176c2a1c54e8b028eeec14bf0a059e354408ff47#code) contracts +Karst is a permissionless and composable social graph built on Starknet, empowering creators to own every part of their social experience. + +With Karst, creators no longer need to worry about losing their content, audience, and livelihood based on the whims of an individual platform's algorithms and policies. + +## Development Setup +You will need to have Scarb and Starknet Foundry installed on your system. Refer to the documentations below: + +- [Starknet Foundry](https://foundry-rs.github.io/starknet-foundry/index.html) +- [Scarb](https://docs.swmansion.com/scarb/download.html) + +To use this repository, first clone it: +``` +git clone git@github.com:horuslabsio/karst-core.git +cd karst-core +``` + +### Building contracts +To build the contracts, run the command: +``` +scarb build +``` + +### Running Tests +To run the tests contained within the `tests` folder, run the command: +``` +snforge test +``` + +### Formatting contracts +We use the in-built formatter that comes with Scarb. To format your contracts, simply run the command: +``` +scarb fmt +``` + +For more information on writing and running tests, refer to the [Starknet-Foundry documentation](https://foundry-rs.github.io/starknet-foundry/index.html) ## Architecture Check out the architecture below, and also reference [lens protocol](https://github.com/lens-protocol/core/tree/master) to understand more. *Architecture Preview.* -![Local Image](./img/karst%20archieture.png) +Screenshot 2024-05-24 at 00 11 16 + + diff --git a/src/interfaces.cairo b/src/interfaces.cairo index 2ed27ce..8ac959f 100644 --- a/src/interfaces.cairo +++ b/src/interfaces.cairo @@ -4,3 +4,4 @@ pub mod IRegistry; pub mod IProfile; pub mod IFollowNFT; pub mod IPublication; +pub mod IHandle; diff --git a/src/interfaces/IHandle.cairo b/src/interfaces/IHandle.cairo new file mode 100644 index 0000000..e4afc89 --- /dev/null +++ b/src/interfaces/IHandle.cairo @@ -0,0 +1,24 @@ +use starknet::ContractAddress; + +// ************************************************************************* +// INTERFACE of FollowNFT +// ************************************************************************* +#[starknet::interface] +pub trait IHandle { + // ************************************************************************* + // EXTERNALS + // ************************************************************************* + fn mint_handle(ref self: TState, address: ContractAddress, local_name: felt252) -> u256; + fn burn_handle(ref self: TState, token_id: u256); + fn set_handle_token_uri(ref self: TState, token_id: u256, local_name: felt252); + fn migrate_handle(ref self: TState, address: ContractAddress, local_name: felt252) -> u256; + // ************************************************************************* + // GETTERS + // ************************************************************************* + fn get_namespace(self: @TState) -> felt252; + fn get_local_name(self: @TState, token_id: u256) -> felt252; + fn get_handle(self: @TState, token_id: u256) -> ByteArray; + fn exists(self: @TState, token_id: u256) -> bool; + fn total_supply(self: @TState) -> u256; + fn get_handle_token_uri(self: @TState, token_id: u256, local_name: felt252) -> ByteArray; +} diff --git a/src/lib.cairo b/src/lib.cairo index e2460a0..9971335 100644 --- a/src/lib.cairo +++ b/src/lib.cairo @@ -6,3 +6,5 @@ pub mod follownft; pub mod mocks; pub mod publication; + +pub mod namespaces; diff --git a/src/namespaces.cairo b/src/namespaces.cairo new file mode 100644 index 0000000..f3d58ad --- /dev/null +++ b/src/namespaces.cairo @@ -0,0 +1,2 @@ +mod handles; +mod handle_registry; diff --git a/src/namespaces/handle_registry.cairo b/src/namespaces/handle_registry.cairo new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/src/namespaces/handle_registry.cairo @@ -0,0 +1 @@ + diff --git a/src/namespaces/handles.cairo b/src/namespaces/handles.cairo new file mode 100644 index 0000000..91aaf97 --- /dev/null +++ b/src/namespaces/handles.cairo @@ -0,0 +1,201 @@ +// ************************************************************************* +// OZ ERC721 +// ************************************************************************* +use openzeppelin::{ + token::erc721::{ERC721Component::{ERC721Metadata, HasComponent}}, + introspection::src5::SRC5Component, +}; + +#[starknet::interface] +trait IERC721Metadata { + fn name(self: @TState) -> ByteArray; + fn symbol(self: @TState) -> ByteArray; +} + +#[starknet::embeddable] +impl IERC721MetadataImpl< + TContractState, + +HasComponent, + +SRC5Component::HasComponent, + +Drop +> of IERC721Metadata { + fn name(self: @TContractState) -> ByteArray { + let component = HasComponent::get_component(self); + ERC721Metadata::name(component) + } + + fn symbol(self: @TContractState) -> ByteArray { + let component = HasComponent::get_component(self); + ERC721Metadata::symbol(component) + } +} + + +#[starknet::contract] +mod Handles { + // ************************************************************************* + // IMPORT + // ************************************************************************* + use core::traits::TryInto; + use starknet::{ContractAddress, get_caller_address}; + use openzeppelin::{ + account, access::ownable::OwnableComponent, + token::erc721::{ + ERC721Component, erc721::ERC721Component::InternalTrait as ERC721InternalTrait + }, + introspection::{src5::SRC5Component} + }; + use karst::interfaces::IHandle::IHandle; + + component!(path: OwnableComponent, storage: ownable, event: OwnableEvent); + component!(path: SRC5Component, storage: src5, event: SRC5Event); + component!(path: ERC721Component, storage: erc721, event: ERC721Event); + + // allow to check what interface is supported + #[abi(embed_v0)] + impl SRC5Impl = SRC5Component::SRC5Impl; + impl SRC5InternalImpl = SRC5Component::InternalImpl; + + #[abi(embed_v0)] + impl ERC721Impl = ERC721Component::ERC721Impl; + #[abi(embed_v0)] + impl ERC721CamelOnlyImpl = ERC721Component::ERC721CamelOnlyImpl; + #[abi(embed_v0)] + impl ERC721MetadataImpl = ERC721Component::ERC721MetadataImpl; + + // add an owner + #[abi(embed_v0)] + impl OwnableImpl = OwnableComponent::OwnableImpl; + impl OwnableInternalImpl = OwnableComponent::InternalImpl; + + // ************************************************************************* + // STORAGE + // ************************************************************************* + #[storage] + struct Storage { + #[substorage(v0)] + erc721: ERC721Component::Storage, + #[substorage(v0)] + src5: SRC5Component::Storage, + #[substorage(v0)] + ownable: OwnableComponent::Storage, + admin: ContractAddress, + total_supply: u256, + local_names: LegacyMap::, + karst_hub: ContractAddress, + } + + // ************************************************************************* + // CONSTANTS + // ************************************************************************* + const MAX_LOCAL_NAME_LENGTH: u256 = 26; + const NAMESPACE: felt252 = 'karst'; + + // ************************************************************************* + // EVENTS + // ************************************************************************* + #[event] + #[derive(Drop, starknet::Event)] + enum Event { + #[flat] + ERC721Event: ERC721Component::Event, + #[flat] + SRC5Event: SRC5Component::Event, + #[flat] + OwnableEvent: OwnableComponent::Event, + } + + // ************************************************************************* + // CONSTRUCTOR + // ************************************************************************* + #[constructor] + fn constructor( + ref self: ContractState, + admin: ContractAddress, // to perform upgrade + name: ByteArray, + symbol: ByteArray, + hub_address: ContractAddress + ) { + self.admin.write(admin); + self.karst_hub.write(hub_address); + self.erc721.initializer("KARST HANDLES", "KARST", ""); + } + + // ************************************************************************* + // EXTERNAL FUNCTIONS + // ************************************************************************* + #[abi(embed_v0)] + impl HandlesImpl of IHandle { + fn mint_handle( + ref self: ContractState, address: ContractAddress, local_name: felt252 + ) -> u256 { + // TODO + return 123; + } + + fn burn_handle(ref self: ContractState, token_id: u256) { // TODO + } + + fn set_handle_token_uri( + ref self: ContractState, token_id: u256, local_name: felt252 + ) { // TODO + } + + fn migrate_handle( + ref self: ContractState, address: ContractAddress, local_name: felt252 + ) -> u256 { + // TODO + return 123; + } + + // ************************************************************************* + // GETTERS + // ************************************************************************* + fn get_namespace(self: @ContractState) -> felt252 { + return NAMESPACE; + } + + fn get_local_name(self: @ContractState, token_id: u256) -> felt252 { + self.local_names.read(token_id) + } + + fn get_handle(self: @ContractState, token_id: u256) -> ByteArray { + // TODO + return "TODO"; + } + + fn exists(self: @ContractState, token_id: u256) -> bool { + self.erc721._exists(token_id) + } + + fn total_supply(self: @ContractState) -> u256 { + self.total_supply.read() + } + + fn get_handle_token_uri( + self: @ContractState, token_id: u256, local_name: felt252 + ) -> ByteArray { + // TODO + return "TODO"; + } + } + + // ************************************************************************* + // PRIVATE FUNCTIONS + // ************************************************************************* + #[generate_trait] + impl Private of PrivateTrait { + fn _mint_handle(address: ContractAddress, local_name: felt252) -> u256 { + // TODO + return 123; + } + + fn _validate_local_name(local_name: felt252) { // TODO + } + + fn _is_alpha_numeric(char: felt252) -> bool { + // TODO + return false; + } + } +}