diff --git a/.eslintrc b/.eslintrc index f76aa747d..f827c32e0 100644 --- a/.eslintrc +++ b/.eslintrc @@ -26,8 +26,7 @@ "quotes": ["error", "single"] }, "ignorePatterns": [ - "contracts/**/scripts/resources", - "scripts/**/resources", + "lib", "cdk.out", "*.js" ] diff --git a/.github/workflows/pull_request.yml b/.github/workflows/pull_request.yml index d547296c4..5805d6067 100644 --- a/.github/workflows/pull_request.yml +++ b/.github/workflows/pull_request.yml @@ -29,12 +29,6 @@ jobs: - name: Install Foundry uses: foundry-rs/foundry-toolchain@v1 - - name: Install mdBook plugins - uses: actions-rs/cargo@v1 - with: - command: install - args: mdbook-katex mdbook-mermaid - - name: Install and build all package dependencies run: npm ci env: diff --git a/.github/workflows/push.yml b/.github/workflows/push.yml index d79bcfa18..faa13dffd 100644 --- a/.github/workflows/push.yml +++ b/.github/workflows/push.yml @@ -36,12 +36,6 @@ jobs: - name: Install Foundry uses: foundry-rs/foundry-toolchain@v1 - - - name: Install mdBook plugins - uses: actions-rs/cargo@v1 - with: - command: install - args: mdbook-katex mdbook-mermaid - name: Install and build all package dependencies run: npm ci diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 63e2f8ca4..dfb16ed2d 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -33,12 +33,6 @@ jobs: - name: Install Foundry uses: foundry-rs/foundry-toolchain@v1 - - name: Install mdBook plugins - uses: actions-rs/cargo@v1 - with: - command: install - args: mdbook-katex mdbook-mermaid - - name: Install and build all package dependencies run: npm ci env: diff --git a/.github/workflows/sandbox.yml b/.github/workflows/sandbox.yml index e2dc03737..74973904f 100644 --- a/.github/workflows/sandbox.yml +++ b/.github/workflows/sandbox.yml @@ -40,12 +40,6 @@ jobs: - name: Install Foundry uses: foundry-rs/foundry-toolchain@v1 - - name: Install mdBook plugins - uses: actions-rs/cargo@v1 - with: - command: install - args: mdbook-katex mdbook-mermaid - - name: Install and build all package dependencies run: npm ci env: diff --git a/.gitmodules b/.gitmodules index 0c911cbe2..05d28266f 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,10 +1,11 @@ -[submodule "contracts/ethereum/scripts/resources/ssv-network"] - path = contracts/ethereum/scripts/resources/ssv-network - url = https://github.com/bloxapp/ssv-network.git - branch = jato-v2 [submodule "contracts/ethereum/lib/forge-std"] path = contracts/ethereum/lib/forge-std url = https://github.com/foundry-rs/forge-std -[submodule "services/oracle/scripts/resources/rockx-dkg-cli"] - path = services/oracle/scripts/resources/rockx-dkg-cli - url = https://github.com/consensusnetworks/rockx-dkg-cli.git +[submodule "contracts/ethereum/lib/ssv-network"] + path = contracts/ethereum/lib/ssv-network + url = https://github.com/bloxapp/ssv-network.git + branch = jato-v2 +[submodule "services/oracle/lib/dkg"] + path = services/oracle/lib/dkg + url = https://github.com/consensusnetworks/ssv-dkg.git + branch = feature/operator-env diff --git a/.vscode/settings.json b/.vscode/settings.json index c8da11d36..960b64ffb 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -19,6 +19,5 @@ "typescript" ], "css.lint.unknownAtRules": "ignore", - "jupyter.notebookFileRoot": "${fileDirname}", "volar.inlayHints.eventArgumentInInlineHandlers": false } \ No newline at end of file diff --git a/README.md b/README.md index 5db9bc1d1..de2e30b4c 100644 --- a/README.md +++ b/README.md @@ -121,8 +121,7 @@ echo "USE_SECRETS=false" > .env | `ETHEREUM_RPC_URL` | Ethereum RPC network URL | `http://127.0.0.1:8545` | | `NETWORK` | Network name (`mainnet || testnet || hardhat || localhost`) | `localhost` | | `FORK` | Fork network name (`mainnet || testnet || hardhat`) | `testnet` | -| `MANAGER_ADDRESS` | Manager contract address | (predicted manager address) | -| `VIEWS_ADDRESS` | Views contract address | (predicted views address) | +| `FACTORY_ADDRESS` | Base factory contract address | (predicted factory address) | | `CRYPTO_COMPARE_API_KEY` | CryptoCompare API key | `` | | `TUNNEL` | Whether to tunnel local network RPC URLs (for remote wallets) | `false` | | `MOCK_SERVICES` | Whether to mock backend services | `true` | diff --git a/apps/docs/.gitignore b/apps/docs/.gitignore new file mode 100644 index 000000000..0c100c4f4 --- /dev/null +++ b/apps/docs/.gitignore @@ -0,0 +1,2 @@ +cache +dist \ No newline at end of file diff --git a/apps/docs/.vitepress/config.mts b/apps/docs/.vitepress/config.mts new file mode 100644 index 000000000..58f968e5f --- /dev/null +++ b/apps/docs/.vitepress/config.mts @@ -0,0 +1,61 @@ +import { withMermaid } from 'vitepress-plugin-mermaid' + +// https://vitepress.dev/reference/site-config +export default withMermaid({ + title: "Casimir Docs", + head: [['link', { rel: 'icon', href: '/favicon.ico' }]], + rewrites: { + 'index.md': 'introduction/what-is-casimir.md', + }, + cleanUrls: true, + markdown: { + math: true + }, + srcDir: 'src', + outDir: './dist', + themeConfig: { + // https://vitepress.dev/reference/default-theme-config + logo: '/casimir.svg', + search: { + provider: 'local' + }, + sidebar: [ + { + text: 'Introduction', + base: '/introduction', + items: [ + { text: 'What is Casimir?', link: '/what-is-casimir' }, + { text: 'Architecture', link: '/architecture' } + ] + }, + { + text: 'Guide', + base: '/guide', + items: [ + { text: 'Accounts', link: '/accounts' }, + { text: 'Staking', link: '/staking' }, + { text: 'Operating', link: '/operating' } + ] + }, + { + text: 'Reference', + base: '/reference', + items: [ + { text: 'Contract Addresses', link: '/contract-addresses' }, + { text: 'Solidity API', link: '/solidity-api' } + ] + }, + { + text: 'Troubleshooting', + base: '/troubleshooting', + items: [ + { text: 'Operator Issues', link: '/operator-issues' }, + { text: 'Wallet Issues', link: '/wallet-issues' } + ] + } + ], + socialLinks: [ + { icon: 'github', link: 'https://github.com/consensusnetworks/casimir' } + ] + } +}) diff --git a/apps/docs/README.md b/apps/docs/README.md new file mode 100644 index 000000000..c0f363bb9 --- /dev/null +++ b/apps/docs/README.md @@ -0,0 +1 @@ +# @casimir/docs diff --git a/apps/docs/package.json b/apps/docs/package.json new file mode 100644 index 000000000..59089cdf6 --- /dev/null +++ b/apps/docs/package.json @@ -0,0 +1,20 @@ +{ + "name": "@casimir/docs", + "private": "true", + "scripts": { + "dev": "npx vitepress dev", + "docgen": "npx esno -r dotenv/config ./scripts/docgen.ts", + "build": "npx vitepress build", + "preview": "npx vitepress preview", + "test": "echo \"Error: no test specified\" && exit 1" + }, + "devDependencies": { + "@types/node": "^17.0.38", + "dotenv": "^16.3.1", + "esno": "0.17.0", + "markdown-it-mathjax3": "^4.3.2", + "mermaid": "^10.5.1", + "vitepress": "^1.0.0-rc.23", + "vitepress-plugin-mermaid": "^2.0.15" + } +} diff --git a/apps/docs/scripts/docgen.ts b/apps/docs/scripts/docgen.ts new file mode 100644 index 000000000..886b96b5a --- /dev/null +++ b/apps/docs/scripts/docgen.ts @@ -0,0 +1,7 @@ +import { run } from '@casimir/shell' + +void async function () { + const outputDir = `${process.cwd()}/src/reference` + const templateDir = `${process.cwd()}/templates/solidity` + await run(`DOCS_OUTPUT_DIR=${outputDir} DOCS_TEMPLATE_DIR=${templateDir} npm run docgen --workspace @casimir/ethereum`) +}() \ No newline at end of file diff --git a/apps/docs/src/guide/accounts.md b/apps/docs/src/guide/accounts.md new file mode 100644 index 000000000..b2cfda29a --- /dev/null +++ b/apps/docs/src/guide/accounts.md @@ -0,0 +1,5 @@ +::: warning +🚧 This page is incomplete. +::: + +# Accounts \ No newline at end of file diff --git a/apps/docs/src/guide/operating.md b/apps/docs/src/guide/operating.md new file mode 100644 index 000000000..eb082a36a --- /dev/null +++ b/apps/docs/src/guide/operating.md @@ -0,0 +1,7 @@ +::: warning +🚧 This page is incomplete. +::: + +# Operating + +Operators owners will need to [set up an SSV node with RockX](https://github.com/consensusnetworks/ssv-dkg) and register with Casimir. The operators page in the Casimir app guides an owner through the process and provides an easy interface for registration and operator management. \ No newline at end of file diff --git a/apps/docs/src/guide/staking.md b/apps/docs/src/guide/staking.md new file mode 100644 index 000000000..d1a3c7fdd --- /dev/null +++ b/apps/docs/src/guide/staking.md @@ -0,0 +1,7 @@ +::: warning +🚧 This page is incomplete. +::: + +# Staking + +Users can deposit any amount of ETH to the manager contract. Their deposits are staked to validators run by SSV operators (see [Operators](#operators)). Rewards are auto-compounded into stake and users can withdraw their principal plus any earned proportion of new stake (or a partial amount of their choice) at any time. \ No newline at end of file diff --git a/apps/docs/src/index.md b/apps/docs/src/index.md new file mode 100644 index 000000000..57a08a27b --- /dev/null +++ b/apps/docs/src/index.md @@ -0,0 +1,17 @@ +::: warning +🚧 This page is incomplete. +::: + +# What is Casimir? + +Casimir is a platform... TODO + +## Casimir Ethereum Staking + +Currlently stakers either need to solo-stake (and have least 32 Ether), or they need to pool their assets in a liquid staking protocol (LSD). While the former choice is a reliably secure choice for Ether holders (if they have solid infrastructure), the latter, LSDs, often present an inherent counterparty risk to the user because of their centralized control of staking node operators (see [The Risks of LSD](https://notes.ethereum.org/@djrtwo/risks-of-lsd)). + +Casimir is designed to offer users the experience and security of solo-staking while pooling their assets. The Casimir contracts seamlessly connect stakers with any amount of Ether to a permissionless registry of high-performing node operators. Casimir aims to minimize counterparty risk for users and improve decentralization in Ethereum staking: + +- Validators duties are performed by registered (collateralized) operators running distributed validator technology (DVT) +- Keys are created and reshared using distributed key generation (DKG) +- Balance and status reports are reported by a decentralized oracle network (DON) \ No newline at end of file diff --git a/apps/docs/src/introduction/architecture.md b/apps/docs/src/introduction/architecture.md new file mode 100644 index 000000000..255888fff --- /dev/null +++ b/apps/docs/src/introduction/architecture.md @@ -0,0 +1,176 @@ +::: warning +🚧 This page is incomplete. +::: + +# Architecture + +Casimir distributes user deposits to Ethereum validators operated by SSV. Validator keys are shared with zero-coordination distributed key generation. Chainlink nodes report from the Beacon chain and SSV to sync balances and rewards, manage collateral recovery, and automate validator creation and exits. + +```mermaid +graph LR + + subgraph Contracts + B(Manager Contract) + C(Beacon Deposit Contract) + D(SSV Contract) + H(Functions Contract) + I(Automation Contract) + end + + subgraph Oracle Dao + G(Oracle) + end + G --> B + + A((User)) --> B + + B --> C + B --> D + + C --> E1(Ethereum Validator 1) + C --> E2(Ethereum Validator 2) + + subgraph Validator 1 + E1 --> F11(SSV Operator 1) + E1 --> F12(SSV Operator 2) + E1 --> F13(SSV Operator 3) + E1 --> F14(SSV Operator 4) + end + + subgraph Validator 2 + E2 --> F21(SSV Operator 5) + E2 --> F22(SSV Operator 6) + E2 --> F23(SSV Operator 7) + E2 --> F24(SSV Operator n) + end + + I --> B + H <--> I + + subgraph Chainlink + J1(Chainlink Node 1) + J2(Chainlink Node 2) + J3(Chainlink Node 3) + J4(Chainlink Node n) + end + + J1 --> H + J2 --> H + J3 --> H + J4 --> H + + J1 --> I + J2 --> I + J3 --> I + J4 --> I +``` + +## Distributed Key Generation + +Casimir distributes validator key shares to operators using SSV nodes with [RockX DKG support](https://github.com/RockX-SG/rockx-dkg-cli). The [@casimir/oracle service](https://github.com/consensusnetworks/casimir/blob/master/services/oracle) uses a DKG messenger server to interact with SSV nodes and perform DKG operations. Before running tests, the [@casimir/oracle generate script](https://github.com/consensusnetworks/casimir/blob/master/services/oracle/scripts/generate.ts) is used to pregenerate DKG keys and the [oracle helper scripts](https://github.com/consensusnetworks/casimir/blob/master/contracts/ethereum/helpers/oracle) completes tests with the pregenerated DKG keys. While running the development environment, a local instance of the @casimir/oracle service is used. + +## Oracles + +The contract uses two oracles to automate the Casimir staking experience and ensure the security of user funds. The automated upkeep contract reports total validator balance, swept balance, and validator actions once per day using [Chainlink Functions](https://docs.chain.link/chainlink-functions) and [Chainlink Automation](https://docs.chain.link/chainlink-automation/introduction). The [@chainlink/functions service](https://github.com/consensusnetworks/casimir/blob/master/services/functions) is used for two request types per report period, balances and details, to overcome the current Chainlink DON constraints. The [Casimir DAO oracle](https://github.com/consensusnetworks/casimir/blob/master/services/oracle) watches the manager contract events and automatically executes zero-coordination distributed key generation (DKG) operations: validator creation, validator resharing, and validator exiting. The DAO oracle also submits verifiable report details in response to reported validator details (such as one or more new exited validators). + +## Users + +Users can deposit any amount of ETH to the manager contract. Their deposits are staked to validators run by SSV operators (see [Operators](#operators)). Rewards are auto-compounded into stake and users can withdraw their principal plus any earned proportion of new stake (or a partial amount of their choice) at any time. + +### User Fees + +The contract charges a user fee on deposits and rewards to cover operational expenses. + +**Fee Distribution Calculation:** + +Let: + +- $F_t$ be the total fee percentage, which is a sum of the required ETH, LINK, and SSV fees. +- $D$ be the amount of ETH deposited by the user. +- $E$ be the amount of ETH to be allocated for the contract's operations. +- $F_a$ be the ETH amount to be swapped for LINK and SSV to facilitate the contract's functions. + +Given the 5% fee, the ETH to be allocated for the contract's operations is calculated as: +$E = D \times \frac{100}{100 + F_t}$ + +The amount to be converted to LINK and SSV is: +$F_a = D - E$ + +Where: + +- $F_t$ typically equals 5%. +- $D$ is the amount of ETH the user wants to deposit. +- $E$ represents the actual ETH amount that will be added to the contract after deducting the fee. +- $F_a$ is the remaining ETH that will be used to acquire LINK and SSV. + +### User Stake + +The manager contract adjusts a user's stake based on the change in the total reward-to-stake ratio sum since their last interaction with the contract. Each time new rewards are reported, the ratio sum is updated to include the new rewards-to-stake ratio. The ratio sum is used to calculate a user's current stake, including compounded rewards, at any time. + +**Current Stake Calculation:** + +Let: + +- $S$ be the calculated current stake of the user, including compounded rewards. +- $S_0$ be the initial stake of the user at the time of their last deposit or stake update. +- $R_s$ be the current cumulative sum of reward-to-stake ratios in the contract. +- $R_{s0}$ be the cumulative sum of reward-to-stake ratios at the time the user made their last deposit or update to their stake. + +The user's current compounded stake at any time is calculated as: +$S = S_0 \times \frac{R_s}{R_{s0}}$ + +Where: + +- $S$ corresponds to **`users[userAddress].stake`** in the contract. +- $S_0$ also corresponds to **`users[userAddress].stake`** in the contract, but it's accessed before settling the user's current stake. +- $R_s$ is represented by **`stakeRatioSum`** in the contract. +- $R_{s0}$ is represented by **`users[userAddress].stakeRatioSum0`** in the contract. + +### User Withdrawals + +Users can request a withdrawal of any amount of their stake at any time. If the requested amount is available in the buffered balance (prepooled balance plus withdrawn balance), the withdrawal is fulfilled immediately. Otherwise, the withdrawal is added to the pending withdrawals queue and fulfilled when the requested amount is available (usually within 1-4 days, depending on the amount). + +## Operators + +Each Casimir validator is run by four selected operators holding the key shares to perform duties with threshold signatures on SSV. Registration is open to any SSV operator (see [Operator Registration](#operator-registration). Operators are selected by an algorithm that ensures high-performance but emphasizes decentralization (see [Operator Selection](#operator-selection)) as user's deposit stake and new validators are required. + +### Operator Registration + +Operators can join the contract registry with a deposit of 4 ETH for collateral (see [Operator Collateral](#operator-collateral)) and a lightweight SSV node config add-on (see [Operator Onboarding](#operator-onboarding)). + +### Operator Selection + +Operators are chosen to run validators based on metrics fetched and derived directly from the SSV network. These metrics are mainly unused collateral (1 ETH per operator per validator), SSV performance, Casimir pool count, and requested fees. + +If an operator owner would like to deregister their operator and free up their collateral, they can request a reshare via the Casimir registry. Casimir removes the operator from existing operator groups by resharing or exiting. The latter is only required in the case that a validator has already undergone more than two reshares to avoid leaving the full key recoverable outside of the currently selected operators. + +### Operator Collateral + +Collateral is used to recover lost validator effective balance at the time of completing an exit. An operator must have at least 1 ETH of available collateral (1 ETH collateral becomes unavailable per each validator that an operator joins) to be selected for a new pool validator. When an operator is removed from a pool, either when resharing or after a completed exit, they are held responsible for up to 1 ETH of the validator's effective balance if any is lost below the 32 ETH minimum. The potential nonzero amount an operator owes in this case is called the blame amount. + +**Blame Amount Calculation:** + +Let: + +- $E$ be the total ETH lost, where $0 \leq E \leq 4$. +- $P_i$ be the performance percentage of the $i^{th}$ operator, where $0 \leq P_i \leq 100$ for $i = 1, 2, 3, 4$. +- $B_i$ be the blame amount for the $i^{th}$ operator. + +If all operators have equal performance, the blame is evenly distributed: +$B_i = \frac{E}{4} \quad \text{for all } i$ + +Otherwise, the blame is distributed inversely proportional to performance: +First, calculate the inverse of each performance: +$I_i = 100 - P_i$ + +Then, the sum of all inverses: +$S = \sum_{i=1}^{4} I_i$ + +Now, the blame for each operator is: +$B_i = \left( \frac{I_i}{S} \right) \times E$ + +The blame amounts are submitted by the DAO oracle in response to a completed validator reshare or exit. + +### Operator Onboarding + +Operators owners will need to [set up an SSV node with RockX](https://github.com/consensusnetworks/ssv-dkg) and register with Casimir. The operators page in the Casimir app guides an owner through the process and provides an easy interface for registration and operator management. \ No newline at end of file diff --git a/apps/docs/src/public/casimir.svg b/apps/docs/src/public/casimir.svg new file mode 100644 index 000000000..19b5fcc26 --- /dev/null +++ b/apps/docs/src/public/casimir.svg @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/apps/docs/src/public/favicon.ico b/apps/docs/src/public/favicon.ico new file mode 100644 index 000000000..4633ba207 Binary files /dev/null and b/apps/docs/src/public/favicon.ico differ diff --git a/apps/docs/src/reference/.gitignore b/apps/docs/src/reference/.gitignore new file mode 100644 index 000000000..2c0bb4406 --- /dev/null +++ b/apps/docs/src/reference/.gitignore @@ -0,0 +1,2 @@ +# Auto-generated API documentation +solidity-api.md \ No newline at end of file diff --git a/apps/docs/src/reference/contract-addresses.md b/apps/docs/src/reference/contract-addresses.md new file mode 100644 index 000000000..a0d25a05d --- /dev/null +++ b/apps/docs/src/reference/contract-addresses.md @@ -0,0 +1,5 @@ +::: warning +🚧 This page is incomplete. +::: + +# Contract Addresses \ No newline at end of file diff --git a/apps/docs/src/troubleshooting/operator-issues.md b/apps/docs/src/troubleshooting/operator-issues.md new file mode 100644 index 000000000..55096e896 --- /dev/null +++ b/apps/docs/src/troubleshooting/operator-issues.md @@ -0,0 +1,5 @@ +::: warning +🚧 This page is incomplete. +::: + +# Operator Issues \ No newline at end of file diff --git a/apps/docs/src/troubleshooting/wallet-issues.md b/apps/docs/src/troubleshooting/wallet-issues.md new file mode 100644 index 000000000..2f2afdc7d --- /dev/null +++ b/apps/docs/src/troubleshooting/wallet-issues.md @@ -0,0 +1,5 @@ +::: warning +🚧 This page is incomplete. +::: + +# Wallet Issues \ No newline at end of file diff --git a/apps/docs/templates/solidity/common.hbs b/apps/docs/templates/solidity/common.hbs new file mode 100644 index 000000000..2a0c87297 --- /dev/null +++ b/apps/docs/templates/solidity/common.hbs @@ -0,0 +1,35 @@ +{{h}} {{name}} + +{{natspec.title}} + +{{#if signature}} +```solidity +{{{signature}}} +``` +{{/if}} + +{{{natspec.notice}}} + +{{#if natspec.dev}} +_{{{natspec.dev}}}_ +{{/if}} + +{{#if natspec.params}} +{{h 2}} Parameters + +| Name | Type | Description | +| ---- | ---- | ----------- | +{{#each params}} +| {{name}} | {{type}} | {{{joinLines natspec}}} | +{{/each}} +{{/if}} + +{{#if natspec.returns}} +{{h 2}} Return Values + +| Name | Type | Description | +| ---- | ---- | ----------- | +{{#each returns}} +| {{#if name}}{{name}}{{else}}[{{@index}}]{{/if}} | {{type}} | {{{joinLines natspec}}} | +{{/each}} +{{/if}} diff --git a/apps/landing/package.json b/apps/landing/package.json index 038f462a3..f97b18c0e 100644 --- a/apps/landing/package.json +++ b/apps/landing/package.json @@ -2,7 +2,6 @@ "name": "@casimir/landing", "description": "Casimir landing page", "private": true, - "version": "0.0.1", "scripts": { "dev": "vite", "build": "vite build", diff --git a/apps/landing/public/favicon.ico b/apps/landing/public/favicon.ico new file mode 100644 index 000000000..4633ba207 Binary files /dev/null and b/apps/landing/public/favicon.ico differ diff --git a/apps/landing/src/App.vue b/apps/landing/src/App.vue index e632401dc..09d38993a 100644 --- a/apps/landing/src/App.vue +++ b/apps/landing/src/App.vue @@ -1,12 +1,7 @@ - \ No newline at end of file +@/composables/blog \ No newline at end of file diff --git a/apps/landing/src/blogs/blog.md b/apps/landing/src/blogs/blog.md deleted file mode 100644 index 3e59d5260..000000000 --- a/apps/landing/src/blogs/blog.md +++ /dev/null @@ -1,11 +0,0 @@ -# Casimir is Live on Goerli! - -10/2/2023 - -We're proud to announce that our distributed, self custody ETH staking platform, Casimir, is live on the ETH Goerli Testnet. Casimir is powered by distributed key generation and distributed validator technology. The Casimir SelfStake manager offers an approach where stakers can directly deposit any amount of ETH to high-performing Ethereum operators. This approach minimizes counterparty risk for users and enhances the decentralization of Ethereum staking: -* Validators duties are performed by openly registered (and collateralized) operators running distributed validator technology (DVT) -* Keys are trustlessly managed using zero-coordination distributed key generation (DKG) -* Automated actions (like compounding stake or handling a slash) are carried out by a decentralized oracle network (DON) -* The staking user experience is improved by wrapping staking contract actions using account abstraction. - -By creating a smart contract primitive that trustlessly connects stakers and node operators, we create a customizable staking process that allows users to access new staking features, such as restaking. Over the next few weeks we'll be introducing some of these additional features, such as restaking, to our platform. diff --git a/apps/landing/src/composables/blog.ts b/apps/landing/src/composables/blog.ts new file mode 100644 index 000000000..669a23fa0 --- /dev/null +++ b/apps/landing/src/composables/blog.ts @@ -0,0 +1,60 @@ +import { onMounted, onUnmounted, readonly, ref } from 'vue' +import snarkdown from 'snarkdown' +import { Article } from '@casimir/types' + +const initializeComposable = ref(false) + +const blogUrl = import.meta.env.PUBLIC_BLOG_URL || 'http://localhost:4001' +const articles = ref([] as Article[]) +const loadingArticles = ref(true) + +export default function useBlog() { + async function getArticleContent(itemId: string) { + const response = await fetch(`${blogUrl}/articles/${itemId}`) + const json = await response.json() + const md = snarkdown(json.content) + return md + } + + onMounted(async () => { + if (!initializeComposable.value) { + loadingArticles.value = true + try { + const response = await fetch(`${blogUrl}/articles`) + const jsonList = await response.json() + const articleList = [] + + for (let i = 0; i < jsonList.length; i++) { + const title = jsonList[i].title + const content = await getArticleContent(jsonList[i].id) + const timestamp = jsonList[i].publishedAt + const id = jsonList[i].id + articleList.push({ + title: title, + content: content, + timestamp: timestamp, + type: 'Blog', + id: id, + }) + } + + articles.value = articleList + loadingArticles.value = false + } catch (error) { + console.log('Error fetching article', error) + loadingArticles.value = false + } + + initializeComposable.value = true + } + }) + + onUnmounted(() => { + initializeComposable.value = false + }) + + return { + articles: readonly(articles), + loadingArticles: readonly(loadingArticles), + } +} diff --git a/apps/landing/src/composables/blogs.ts b/apps/landing/src/composables/blogs.ts deleted file mode 100644 index cd106b039..000000000 --- a/apps/landing/src/composables/blogs.ts +++ /dev/null @@ -1,67 +0,0 @@ -import { onMounted, onUnmounted, readonly, ref } from 'vue' -import snarkdown from 'snarkdown' - -const initializeComposable = ref(false) - -type blog = { - title: string; - content: string; - timestamp: string; - type: string; - id: string; -}; - -const allBlogs = ref([] as blog[]) -const loadingBlogs = ref(true) - -export default function useBlogs() { - async function getContentOfBlog(itemId: string) { - const response = await fetch(`http://localhost:3003/api/hackmd/${itemId}`) - const json = await response.json() - const md = snarkdown(json.content) - - return md - } - - onMounted(async () => { - if (!initializeComposable.value) { - loadingBlogs.value = true - try { - const response = await fetch('http://localhost:3003/api/hackmd') - const jsonList = await response.json() - const blogList = [] - - for (let i = 0; i < jsonList.length; i++) { - const title = jsonList[i].title - const content = await getContentOfBlog(jsonList[i].id) - const timestamp = jsonList[i].publishedAt - const id = jsonList[i].id - blogList.push({ - title: title, - content: content, - timestamp: timestamp, - type: 'Blog', - id: id, - }) - } - - allBlogs.value = blogList - loadingBlogs.value = false - } catch (error) { - console.error('Error trying to fetch:', error) - loadingBlogs.value = false - } - - initializeComposable.value = true - } - }) - - onUnmounted(() => { - initializeComposable.value = false - }) - - return { - allBlogs: readonly(allBlogs), - loadingBlogs: readonly(loadingBlogs), - } -} diff --git a/apps/landing/src/composables/router.ts b/apps/landing/src/composables/router.ts index 6a5268415..962428b3e 100644 --- a/apps/landing/src/composables/router.ts +++ b/apps/landing/src/composables/router.ts @@ -2,35 +2,35 @@ import { createWebHistory, createRouter } from 'vue-router' import Landing from '@/pages/landing/Landing.vue' import Changelog from '@/pages/changelog/Changelog.vue' import Blog from '@/pages/blog/Blog.vue' -import SingleBlog from '@/pages/blog/components/SingleBlog.vue' +import Article from '@/pages/blog/components/Article.vue' const routes = [ - { - path: '/', - name: Landing, - component: Landing, - }, - { - path: '/blogs', - name: Blog, - component: Blog, - }, - { - path: '/blog/:id', - component: SingleBlog, - children: [{ path: '', name: SingleBlog, component: SingleBlog }], - }, - { - path: '/changelog', - name: Changelog, - component: Changelog, - }, + { + path: '/', + name: Landing, + component: Landing, + }, + { + path: '/blog', + name: Blog, + component: Blog, + }, + { + path: '/blog/:id', + component: Article, + children: [{ path: '', name: Article, component: Article }], + }, + { + path: '/changelog', + name: Changelog, + component: Changelog, + }, ] const router = createRouter({ - history: createWebHistory(), - // eslint-disable-next-line @typescript-eslint/ban-ts-comment - // @ts-ignore - routes, + history: createWebHistory(), + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore + routes, }) export default router diff --git a/apps/landing/src/pages/blog/Blog.vue b/apps/landing/src/pages/blog/Blog.vue index 379880d6c..54bcfe2c7 100644 --- a/apps/landing/src/pages/blog/Blog.vue +++ b/apps/landing/src/pages/blog/Blog.vue @@ -1,12 +1,11 @@ - @@ -25,7 +24,7 @@ const { API Reference
  • - Blog + Blog
  • Changelog @@ -44,35 +43,35 @@ const {
    - {{ blog.type }} • + {{ article.type }} • - {{ new Date(blog.timestamp).toDateString() }} + {{ new Date(article.timestamp).toDateString() }}
    - {{ blog.title }} + {{ article.title }}
    @@ -125,33 +124,33 @@ const { \ No newline at end of file diff --git a/apps/landing/src/pages/blog/components/SingleBlog.vue b/apps/landing/src/pages/blog/components/Article.vue similarity index 71% rename from apps/landing/src/pages/blog/components/SingleBlog.vue rename to apps/landing/src/pages/blog/components/Article.vue index 0b104cf12..b03de42bf 100644 --- a/apps/landing/src/pages/blog/components/SingleBlog.vue +++ b/apps/landing/src/pages/blog/components/Article.vue @@ -1,37 +1,36 @@ - @@ -51,7 +50,7 @@ watch([allBlogs, loadingBlogs], () => { API Reference
  • - Blog + Blog
  • Changelog @@ -71,7 +70,7 @@ watch([allBlogs, loadingBlogs], () => {
    @@ -83,7 +82,7 @@ watch([allBlogs, loadingBlogs], () => {
    @@ -106,10 +105,10 @@ watch([allBlogs, loadingBlogs], () => {
    - {{ activeBlog.type }} + {{ article.type }}
    - {{ new Date(activeBlog.timestamp).toDateString() }} + {{ new Date(article.timestamp).toDateString() }}
    @@ -117,7 +116,7 @@ watch([allBlogs, loadingBlogs], () => {
    @@ -160,32 +159,32 @@ watch([allBlogs, loadingBlogs], () => { \ No newline at end of file diff --git a/apps/landing/src/pages/changelog/Changelog.vue b/apps/landing/src/pages/changelog/Changelog.vue index 09edf5ff0..3aa5d79cc 100644 --- a/apps/landing/src/pages/changelog/Changelog.vue +++ b/apps/landing/src/pages/changelog/Changelog.vue @@ -92,7 +92,7 @@ document.addEventListener('DOMContentLoaded', async () => { API Reference
  • -
  • Blog
  • +
  • Blog
  • import { ref } from 'vue' - +const docsUrl = import.meta.env.PUBLIC_DOCS_URL const faqItems = ref( [ { @@ -74,7 +74,7 @@ const toggleQuestionItem = (index: number) => { >API Reference
  • - Blog + Blog
  • Changelog @@ -331,9 +331,9 @@ const toggleQuestionItem = (index: number) => {