From 3e288c4cfd4d3c1766bb353d027fbdbf11645c6b Mon Sep 17 00:00:00 2001 From: Jamie Harding Date: Mon, 10 Feb 2025 16:00:08 +0100 Subject: [PATCH 01/49] Add getTransaction --- crates/js_api/src/subgraph/mod.rs | 1 + crates/js_api/src/subgraph/transaction.rs | 16 ++++++++++ crates/subgraph/src/orderbook_client.rs | 14 +++++++++ crates/subgraph/src/types/mod.rs | 1 + crates/subgraph/src/types/transaction.rs | 12 +++++++ .../orderbook/test/js_api/transaction.test.ts | 31 +++++++++++++++++++ 6 files changed, 75 insertions(+) create mode 100644 crates/js_api/src/subgraph/transaction.rs create mode 100644 crates/subgraph/src/types/transaction.rs create mode 100644 packages/orderbook/test/js_api/transaction.test.ts diff --git a/crates/js_api/src/subgraph/mod.rs b/crates/js_api/src/subgraph/mod.rs index 3241b9c16..7df2bad7f 100644 --- a/crates/js_api/src/subgraph/mod.rs +++ b/crates/js_api/src/subgraph/mod.rs @@ -8,6 +8,7 @@ use thiserror::Error; use wasm_bindgen::{JsError, JsValue}; pub mod order; +pub mod transaction; pub mod vault; #[derive(Error, Debug)] diff --git a/crates/js_api/src/subgraph/transaction.rs b/crates/js_api/src/subgraph/transaction.rs new file mode 100644 index 000000000..d629860b4 --- /dev/null +++ b/crates/js_api/src/subgraph/transaction.rs @@ -0,0 +1,16 @@ +use cynic::Id; +use rain_orderbook_bindings::wasm_traits::prelude::*; +use rain_orderbook_subgraph_client::{OrderbookSubgraphClient, OrderbookSubgraphClientError}; +use reqwest::Url; + +/// Internal function to fetch a single transaction +/// Returns the Transaction struct +#[wasm_bindgen(js_name = "getTransaction")] +pub async fn get_sg_transaction( + url: &str, + id: &str, +) -> Result { + let client = OrderbookSubgraphClient::new(Url::parse(url)?); + let transaction = client.transaction_detail(Id::new(id)).await?; + Ok(to_value(&transaction)?) +} diff --git a/crates/subgraph/src/orderbook_client.rs b/crates/subgraph/src/orderbook_client.rs index fbcb74ac5..4d83760e5 100644 --- a/crates/subgraph/src/orderbook_client.rs +++ b/crates/subgraph/src/orderbook_client.rs @@ -8,6 +8,7 @@ use crate::types::order::{ OrdersListQuery, }; use crate::types::order_trade::{OrderTradeDetailQuery, OrderTradesListQuery}; +use crate::types::transaction::TransactionDetailQuery; use crate::types::vault::{VaultDetailQuery, VaultsListQuery}; use crate::vault_balance_changes_query::VaultBalanceChangesListPageQueryClient; use cynic::Id; @@ -379,4 +380,17 @@ impl OrderbookSubgraphClient { } Ok(all_pages_merged) } + + pub async fn transaction_detail( + &self, + id: Id, + ) -> Result { + let data = self + .query::(IdQueryVariables { id: &id }) + .await?; + let transaction = data + .transaction + .ok_or(OrderbookSubgraphClientError::Empty)?; + Ok(transaction) + } } diff --git a/crates/subgraph/src/types/mod.rs b/crates/subgraph/src/types/mod.rs index b653da9b9..aa590dc15 100644 --- a/crates/subgraph/src/types/mod.rs +++ b/crates/subgraph/src/types/mod.rs @@ -3,6 +3,7 @@ mod impls; pub mod order; pub mod order_detail_traits; pub mod order_trade; +pub mod transaction; pub mod vault; pub use cynic::Id; diff --git a/crates/subgraph/src/types/transaction.rs b/crates/subgraph/src/types/transaction.rs new file mode 100644 index 000000000..7344b5f23 --- /dev/null +++ b/crates/subgraph/src/types/transaction.rs @@ -0,0 +1,12 @@ +use super::common::*; +use crate::schema; +use typeshare::typeshare; + +#[derive(cynic::QueryFragment, Debug)] +#[cynic(graphql_type = "Query", variables = "IdQueryVariables")] +#[typeshare] +pub struct TransactionDetailQuery { + #[arguments(id: $id)] + #[typeshare(typescript(type = "TransactionSubgraph"))] + pub transaction: Option, +} diff --git a/packages/orderbook/test/js_api/transaction.test.ts b/packages/orderbook/test/js_api/transaction.test.ts new file mode 100644 index 000000000..809db66bb --- /dev/null +++ b/packages/orderbook/test/js_api/transaction.test.ts @@ -0,0 +1,31 @@ +import assert from 'assert'; +import { getLocal } from 'mockttp'; +import { describe, it, beforeEach, afterEach } from 'vitest'; +import { Transaction } from '../../dist/types/js_api.js'; +import { getTransaction } from '../../dist/cjs/js_api.js'; + +const transaction1 = { + id: 'tx1', + from: '0x1', + blockNumber: '1', + timestamp: '1' +} as unknown as Transaction; + +describe('Rain Orderbook JS API Package Bindgen Tests - Order', async function () { + const mockServer = getLocal(); + beforeEach(() => mockServer.start(8090)); + afterEach(() => mockServer.stop()); + + it('should fetch a single transaction', async () => { + await mockServer + .forPost('/sg1') + .thenReply(200, JSON.stringify({ data: { transaction: transaction1 } })); + + try { + const result: Transaction = await getTransaction(mockServer.url + '/sg1', transaction1.id); + assert.equal(result.id, transaction1.id); + } catch (e) { + assert.fail('expected to resolve, but failed' + (e instanceof Error ? e.message : String(e))); + } + }); +}); From 8bd686d51a16c2b18c307e5c060f6109feb0b099 Mon Sep 17 00:00:00 2001 From: Jamie Harding Date: Tue, 11 Feb 2025 11:34:58 +0100 Subject: [PATCH 02/49] getting the tc and polling --- crates/js_api/src/subgraph/transaction.rs | 6 +-- crates/subgraph/src/orderbook_client.rs | 2 + .../detail/DepositOrWithdrawButtons.svelte | 8 +++- .../lib/components/detail/OrderDetail.svelte | 3 ++ .../lib/components/detail/VaultDetail.svelte | 2 + .../src/lib/stores/transactionStore.ts | 47 +++++++++++++++---- .../components/DepositOrWithdrawModal.svelte | 7 ++- packages/webapp/src/lib/services/modal.ts | 1 + 8 files changed, 61 insertions(+), 15 deletions(-) diff --git a/crates/js_api/src/subgraph/transaction.rs b/crates/js_api/src/subgraph/transaction.rs index d629860b4..cb173586d 100644 --- a/crates/js_api/src/subgraph/transaction.rs +++ b/crates/js_api/src/subgraph/transaction.rs @@ -6,11 +6,11 @@ use reqwest::Url; /// Internal function to fetch a single transaction /// Returns the Transaction struct #[wasm_bindgen(js_name = "getTransaction")] -pub async fn get_sg_transaction( +pub async fn get_transaction( url: &str, - id: &str, + tx_hash: &str, ) -> Result { let client = OrderbookSubgraphClient::new(Url::parse(url)?); - let transaction = client.transaction_detail(Id::new(id)).await?; + let transaction = client.transaction_detail(Id::new(tx_hash)).await?; Ok(to_value(&transaction)?) } diff --git a/crates/subgraph/src/orderbook_client.rs b/crates/subgraph/src/orderbook_client.rs index 4d83760e5..aa902c629 100644 --- a/crates/subgraph/src/orderbook_client.rs +++ b/crates/subgraph/src/orderbook_client.rs @@ -26,6 +26,8 @@ pub enum OrderbookSubgraphClientError { CynicClientError(#[from] CynicClientError), #[error("Subgraph query returned no data")] Empty, + #[error("Request timed out")] + RequestTimedOut, #[error(transparent)] PaginationClientError(#[from] PaginationClientError), #[error(transparent)] diff --git a/packages/ui-components/src/lib/components/detail/DepositOrWithdrawButtons.svelte b/packages/ui-components/src/lib/components/detail/DepositOrWithdrawButtons.svelte index 0b70c7e1b..7e5472c60 100644 --- a/packages/ui-components/src/lib/components/detail/DepositOrWithdrawButtons.svelte +++ b/packages/ui-components/src/lib/components/detail/DepositOrWithdrawButtons.svelte @@ -10,12 +10,14 @@ action: 'deposit' | 'withdraw'; chainId: number; rpcUrl: string; + subgraphUrl: string; }) => void; export let vault: Vault; export let chainId: number; export let rpcUrl: string; export let query: CreateQueryResult; + export let subgraphUrl: string; diff --git a/packages/ui-components/src/lib/components/detail/OrderDetail.svelte b/packages/ui-components/src/lib/components/detail/OrderDetail.svelte index 21f550cb4..8f9780854 100644 --- a/packages/ui-components/src/lib/components/detail/OrderDetail.svelte +++ b/packages/ui-components/src/lib/components/detail/OrderDetail.svelte @@ -31,6 +31,7 @@ action: 'deposit' | 'withdraw'; chainId: number; rpcUrl: string; + subgraphUrl: string; }) => void) | undefined = undefined; export let handleOrderRemoveModal: @@ -146,6 +147,7 @@ {rpcUrl} query={orderDetailQuery} {handleDepositOrWithdrawModal} + {subgraphUrl} /> {/if} {/each} @@ -169,6 +171,7 @@ {rpcUrl} query={orderDetailQuery} {handleDepositOrWithdrawModal} + {subgraphUrl} /> {/if} {/each} diff --git a/packages/ui-components/src/lib/components/detail/VaultDetail.svelte b/packages/ui-components/src/lib/components/detail/VaultDetail.svelte index 4ba56f815..70ea6fac3 100644 --- a/packages/ui-components/src/lib/components/detail/VaultDetail.svelte +++ b/packages/ui-components/src/lib/components/detail/VaultDetail.svelte @@ -30,6 +30,7 @@ action: 'deposit' | 'withdraw'; chainId: number; rpcUrl: string; + subgraphUrl: string }) => void) | undefined = undefined; export let id: string; @@ -97,6 +98,7 @@ {rpcUrl} query={vaultDetailQuery} {handleDepositOrWithdrawModal} + {subgraphUrl} /> {:else if handleDepositModal && handleWithdrawModal && $walletAddressMatchesOrBlank?.(data.owner)} @@ -79,7 +83,7 @@

diff --git a/packages/webapp/src/routes/deploy/[strategyName]/+layout.ts b/packages/webapp/src/routes/deploy/[strategyName]/+layout.ts index 8b53d4fb1..b5ae39337 100644 --- a/packages/webapp/src/routes/deploy/[strategyName]/+layout.ts +++ b/packages/webapp/src/routes/deploy/[strategyName]/+layout.ts @@ -1,32 +1,20 @@ -import { registryUrl } from '$lib/stores/registry'; import { rawDotrain } from '$lib/stores/raw-dotrain'; -import { DotrainOrderGui } from '@rainlanguage/orderbook/js_api'; import { get } from 'svelte/store'; import type { LayoutLoad } from './$types'; import { redirect } from '@sveltejs/kit'; +import { getFileRegistry } from '$lib/services/getFileRegistry'; export const load: LayoutLoad = async ({ fetch, params, parent }) => { - const { strategyName, deploymentKey } = params; + const { strategyName } = params; const { registry } = await parent(); - if (registry) { - registryUrl.set(registry); - } + + let dotrain; + try { - let dotrain; if (strategyName === 'raw' && get(rawDotrain)) { dotrain = get(rawDotrain); } else { - const _registryUrl = get(registryUrl); - const response = await fetch(_registryUrl); - const files = await response.text(); - - const fileList = files - .split('\n') - .filter(Boolean) - .map((line: string) => { - const [name, url] = line.split(' '); - return { name, url }; - }); + const fileList = await getFileRegistry(registry); const strategy = fileList.find((file: { name: string }) => file.name === strategyName); if (!strategy) { @@ -36,33 +24,12 @@ export const load: LayoutLoad = async ({ fetch, params, parent }) => { const dotrainResponse = await fetch(strategy.url); dotrain = await dotrainResponse.text(); } - - // Process deployments for both raw and registry strategies - const deploymentWithDetails = await DotrainOrderGui.getDeploymentDetails(dotrain); - const deployments = Array.from(deploymentWithDetails, ([key, details]) => ({ - key, - ...details - })); - - const deployment = deployments.find( - (deployment: { key: string }) => deployment.key === deploymentKey - ); - - if (!deployment) { - throw new Error(`Deployment ${deploymentKey} not found`); - } - - const { key, name, description } = deployment; - - return { - dotrain, - strategyName, - key, - name, - description, - deployment - }; } catch { throw redirect(307, '/deploy'); } + + return { + dotrain, + strategyName + }; }; diff --git a/packages/webapp/src/routes/deploy/[strategyName]/+page.svelte b/packages/webapp/src/routes/deploy/[strategyName]/+page.svelte index 63c22bbf6..26dbe5c4b 100644 --- a/packages/webapp/src/routes/deploy/[strategyName]/+page.svelte +++ b/packages/webapp/src/routes/deploy/[strategyName]/+page.svelte @@ -1,33 +1,8 @@ -
-
-
-

- {strategyDetails.name} -

-
{strategyDetails.short_description}
- {#if isMarkdownUrl(strategyDetails.description) && markdownContent} -
- -
- {:else} -

- {strategyDetails.description} -

- {/if} -
- -
-
+ diff --git a/packages/webapp/src/routes/deploy/[strategyName]/[deploymentKey]/+layout.ts b/packages/webapp/src/routes/deploy/[strategyName]/[deploymentKey]/+layout.ts new file mode 100644 index 000000000..c6e9f976b --- /dev/null +++ b/packages/webapp/src/routes/deploy/[strategyName]/[deploymentKey]/+layout.ts @@ -0,0 +1,24 @@ +import { DotrainOrderGui } from '@rainlanguage/orderbook/js_api'; +import type { LayoutLoad } from '../$types'; + +export const load: LayoutLoad = async ({ params, parent }) => { + const { deploymentKey } = params; + const { dotrain } = await parent(); + + // Process deployments for both raw and registry strategies + const deploymentWithDetails = await DotrainOrderGui.getDeploymentDetails(dotrain); + const deployments = Array.from(deploymentWithDetails, ([key, details]) => ({ + key, + ...details + })); + + const deployment = deployments.find( + (deployment: { key: string }) => deployment.key === deploymentKey + ); + + if (!deployment) { + throw new Error(`Deployment ${deploymentKey} not found`); + } + + return { deployment, dotrain }; +}; diff --git a/packages/webapp/src/routes/deploy/[strategyName]/[deploymentKey]/+page.svelte b/packages/webapp/src/routes/deploy/[strategyName]/[deploymentKey]/+page.svelte index c54831e6f..6e5784d62 100644 --- a/packages/webapp/src/routes/deploy/[strategyName]/[deploymentKey]/+page.svelte +++ b/packages/webapp/src/routes/deploy/[strategyName]/[deploymentKey]/+page.svelte @@ -1,31 +1,32 @@ -{#if !dotrain || !key} +{#if !dotrain || !deployment}
Deployment not found. Redirecting to deployments page...
{:else} - {/if} From 391df8e2a078a5883cc4d6d16464a14d69b82537 Mon Sep 17 00:00:00 2001 From: Jamie Harding Date: Thu, 13 Feb 2025 11:10:21 +0100 Subject: [PATCH 15/49] invalidate queries on tx success --- .../src/routes/vaults/[network]-[id]/+page.svelte | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/packages/webapp/src/routes/vaults/[network]-[id]/+page.svelte b/packages/webapp/src/routes/vaults/[network]-[id]/+page.svelte index 3f0744d9e..b8d38baf8 100644 --- a/packages/webapp/src/routes/vaults/[network]-[id]/+page.svelte +++ b/packages/webapp/src/routes/vaults/[network]-[id]/+page.svelte @@ -1,11 +1,21 @@ From affd03d85600dd7456ad73c1f1a3283d6c244d86 Mon Sep 17 00:00:00 2001 From: Jamie Harding Date: Thu, 13 Feb 2025 11:12:13 +0100 Subject: [PATCH 16/49] WIP live update --- .../components/charts/LightweightChart.svelte | 23 ++++++++++++++++--- 1 file changed, 20 insertions(+), 3 deletions(-) diff --git a/packages/ui-components/src/lib/components/charts/LightweightChart.svelte b/packages/ui-components/src/lib/components/charts/LightweightChart.svelte index 599d63698..918a2a807 100644 --- a/packages/ui-components/src/lib/components/charts/LightweightChart.svelte +++ b/packages/ui-components/src/lib/components/charts/LightweightChart.svelte @@ -45,6 +45,7 @@ let timeDelta: number; let timeFrom: UTCTimestamp; let timeTo: UTCTimestamp; + let previousDataLength = 0; function setTimeScale() { if (chart === undefined) return; @@ -70,9 +71,25 @@ }); } - function setData() { + function updateNewDataPoints() { + console.log('updating new data points'); if (series === undefined || data.length === 0) return; - series.setData(data); + + // If this is the first data set, set all the data + if (previousDataLength === 0) { + series.setData(data); + console.log(data.length, previousDataLength); + } + // If we have new data points, only update the new ones + else if (data.length > previousDataLength) { + console.log('new data points!'); + const newPoints = data.slice(previousDataLength); + newPoints.forEach((point) => { + series?.update(point); + }); + } + + previousDataLength = data.length; setTimeScale(); } @@ -91,7 +108,7 @@ setOptions(); } - $: if (data || series) setData(); + $: if (data || series) updateNewDataPoints(); $: if (timeDelta) setTimeScale(); $: if ($lightweightChartsTheme) setOptions(); From 8690ff32eee22498d94a2b2834443c9ef36d98e0 Mon Sep 17 00:00:00 2001 From: Jamie Harding Date: Thu, 13 Feb 2025 11:38:33 +0100 Subject: [PATCH 17/49] wip, trying to get chart updating automatically without re-rendering --- .../TanstackLightweightChartLine.svelte | 2 ++ .../charts/VaultBalanceChart.svelte | 4 +++- .../lib/components/detail/VaultDetail.svelte | 2 +- .../tables/VaultBalanceChangesTable.svelte | 7 +++++-- .../routes/vaults/[network]-[id]/+page.svelte | 19 +++++++++++++++---- 5 files changed, 26 insertions(+), 8 deletions(-) diff --git a/packages/ui-components/src/lib/components/charts/TanstackLightweightChartLine.svelte b/packages/ui-components/src/lib/components/charts/TanstackLightweightChartLine.svelte index 16755cd93..a6bd34ee4 100644 --- a/packages/ui-components/src/lib/components/charts/TanstackLightweightChartLine.svelte +++ b/packages/ui-components/src/lib/components/charts/TanstackLightweightChartLine.svelte @@ -26,6 +26,8 @@ $: data = transformAndSortData($query.data ?? []); + $: console.log('data changed in chart', data); + const createSeries = (chart: IChartApi) => chart.addLineSeries({ lineWidth: 1 }); diff --git a/packages/ui-components/src/lib/components/charts/VaultBalanceChart.svelte b/packages/ui-components/src/lib/components/charts/VaultBalanceChart.svelte index 93a7ab5f5..f97063a63 100644 --- a/packages/ui-components/src/lib/components/charts/VaultBalanceChart.svelte +++ b/packages/ui-components/src/lib/components/charts/VaultBalanceChart.svelte @@ -14,12 +14,14 @@ import { QKEY_VAULT_CHANGES } from '../../queries/keys'; export let vault: Vault; + export let id: string; export let subgraphUrl: string; export let lightweightChartsTheme; $: query = createQuery({ - queryKey: [QKEY_VAULT_CHANGES, vault], + queryKey: [id, QKEY_VAULT_CHANGES + id, QKEY_VAULT_CHANGES], queryFn: () => { + console.log('✅ geting vault balance chart'); return getVaultBalanceChanges(subgraphUrl || '', vault.id, { page: 1, pageSize: 1000 diff --git a/packages/ui-components/src/lib/components/detail/VaultDetail.svelte b/packages/ui-components/src/lib/components/detail/VaultDetail.svelte index 2821560ed..63bdca277 100644 --- a/packages/ui-components/src/lib/components/detail/VaultDetail.svelte +++ b/packages/ui-components/src/lib/components/detail/VaultDetail.svelte @@ -193,7 +193,7 @@ - + diff --git a/packages/ui-components/src/lib/components/tables/VaultBalanceChangesTable.svelte b/packages/ui-components/src/lib/components/tables/VaultBalanceChangesTable.svelte index ecf61eb4f..9cbbe15f5 100644 --- a/packages/ui-components/src/lib/components/tables/VaultBalanceChangesTable.svelte +++ b/packages/ui-components/src/lib/components/tables/VaultBalanceChangesTable.svelte @@ -18,9 +18,12 @@ export let id: string; export let subgraphUrl: string; + $: console.log('ID in table', id); + $: balanceChangesQuery = createInfiniteQuery({ - queryKey: [id, QKEY_VAULT_CHANGES + id], + queryKey: [id, QKEY_VAULT_CHANGES + id, QKEY_VAULT_CHANGES], queryFn: ({ pageParam }) => { + console.log('GETTING VAULT BALANCE CHANGES'); return getVaultBalanceChanges(subgraphUrl || '', id, { page: pageParam + 1, pageSize: DEFAULT_PAGE_SIZE @@ -28,7 +31,7 @@ }, initialPageParam: 0, getNextPageParam(lastPage, _allPages, lastPageParam) { - return lastPage.length === DEFAULT_PAGE_SIZE ? lastPageParam + 1 : undefined; + return lastPage?.length === DEFAULT_PAGE_SIZE ? lastPageParam + 1 : undefined; }, enabled: !!subgraphUrl }); diff --git a/packages/webapp/src/routes/vaults/[network]-[id]/+page.svelte b/packages/webapp/src/routes/vaults/[network]-[id]/+page.svelte index b8d38baf8..0bf28af8f 100644 --- a/packages/webapp/src/routes/vaults/[network]-[id]/+page.svelte +++ b/packages/webapp/src/routes/vaults/[network]-[id]/+page.svelte @@ -1,5 +1,11 @@ From a654c20cd3e0b9b40165ffcfa82c6bc42a1d7ceb Mon Sep 17 00:00:00 2001 From: Jamie Harding Date: Thu, 13 Feb 2025 12:58:28 +0100 Subject: [PATCH 18/49] invalidate queries and show toast --- .../routes/vaults/[network]-[id]/+page.svelte | 46 ++++++++++++------- 1 file changed, 30 insertions(+), 16 deletions(-) diff --git a/packages/webapp/src/routes/vaults/[network]-[id]/+page.svelte b/packages/webapp/src/routes/vaults/[network]-[id]/+page.svelte index 0bf28af8f..73ede09ff 100644 --- a/packages/webapp/src/routes/vaults/[network]-[id]/+page.svelte +++ b/packages/webapp/src/routes/vaults/[network]-[id]/+page.svelte @@ -1,36 +1,50 @@ +{#if toastOpen} + + + Vault balance updated + Autohide in {counter}s. + +{/if} + Date: Thu, 13 Feb 2025 12:39:33 +0000 Subject: [PATCH 19/49] routing and cleanup --- packages/ui-components/package.json | 6 + ...gySection.test.ts => StrategyPage.test.ts} | 10 +- .../deployment/DeploymentSteps.svelte | 1 + .../deployment/DeploymentTile.svelte | 14 +- ...tegySection.svelte => StrategyPage.svelte} | 39 ++--- .../deployment/StrategyShortTile.svelte | 24 +++ packages/ui-components/src/lib/index.ts | 6 +- .../ui-components/src/lib/services/index.ts | 2 + .../src/lib/services/registry.ts} | 43 +++++- packages/webapp/src/routes/deploy/+layout.ts | 13 +- .../webapp/src/routes/deploy/+page.svelte | 137 +++++------------- .../deploy/[strategyName]/+layout.svelte | 7 - .../routes/deploy/[strategyName]/+layout.ts | 15 +- .../routes/deploy/[strategyName]/+page.svelte | 6 +- .../[deploymentKey]/+page.svelte | 33 ++++- 15 files changed, 191 insertions(+), 165 deletions(-) rename packages/ui-components/src/__tests__/{StrategySection.test.ts => StrategyPage.test.ts} (95%) rename packages/ui-components/src/lib/components/deployment/{StrategySection.svelte => StrategyPage.svelte} (67%) create mode 100644 packages/ui-components/src/lib/components/deployment/StrategyShortTile.svelte create mode 100644 packages/ui-components/src/lib/services/index.ts rename packages/{webapp/src/lib/services/getFileRegistry.ts => ui-components/src/lib/services/registry.ts} (61%) delete mode 100644 packages/webapp/src/routes/deploy/[strategyName]/+layout.svelte diff --git a/packages/ui-components/package.json b/packages/ui-components/package.json index b37deba33..9225134a3 100644 --- a/packages/ui-components/package.json +++ b/packages/ui-components/package.json @@ -12,6 +12,12 @@ "import": "./dist/index.js", "require": "./dist/index.js", "svelte": "./dist/index.js" + }, + "./services": { + "types": "./dist/services/index.d.ts", + "import": "./dist/services/index.js", + "require": "./dist/services/index.js", + "svelte": "./dist/services/index.js" } }, "scripts": { diff --git a/packages/ui-components/src/__tests__/StrategySection.test.ts b/packages/ui-components/src/__tests__/StrategyPage.test.ts similarity index 95% rename from packages/ui-components/src/__tests__/StrategySection.test.ts rename to packages/ui-components/src/__tests__/StrategyPage.test.ts index b7a3be6ad..f7fc5fd26 100644 --- a/packages/ui-components/src/__tests__/StrategySection.test.ts +++ b/packages/ui-components/src/__tests__/StrategyPage.test.ts @@ -1,5 +1,5 @@ import { render, screen, waitFor } from '@testing-library/svelte'; -import StrategySection from '../lib/components/deployment/StrategySection.svelte'; +import StrategyPage from '../lib/components/deployment/StrategyPage.svelte'; import { DotrainOrderGui } from '@rainlanguage/orderbook/js_api'; import { vi, describe, it, expect, beforeEach } from 'vitest'; @@ -33,7 +33,7 @@ describe('StrategySection', () => { }; vi.mocked(DotrainOrderGui.getStrategyDetails).mockResolvedValueOnce(mockStrategyDetails); - render(StrategySection, { + render(StrategyPage, { props: { rawDotrain: mockDotrain } @@ -60,7 +60,7 @@ describe('StrategySection', () => { // Mock DotrainOrderGui methods vi.mocked(DotrainOrderGui.getStrategyDetails).mockResolvedValueOnce(mockStrategyDetails); - render(StrategySection, { + render(StrategyPage, { props: { strategyUrl: 'http://example.com/strategy', strategyName: 'TestStrategy' @@ -85,7 +85,7 @@ describe('StrategySection', () => { // Mock DotrainOrderGui methods vi.mocked(DotrainOrderGui.getStrategyDetails).mockRejectedValueOnce(mockError); - render(StrategySection, { + render(StrategyPage, { props: { strategyUrl: 'http://example.com/strategy', strategyName: 'TestStrategy' @@ -104,7 +104,7 @@ describe('StrategySection', () => { // Mock fetch to reject mockFetch.mockRejectedValueOnce(mockError); - render(StrategySection, { + render(StrategyPage, { props: { strategyUrl: 'http://example.com/strategy', strategyName: 'TestStrategy' diff --git a/packages/ui-components/src/lib/components/deployment/DeploymentSteps.svelte b/packages/ui-components/src/lib/components/deployment/DeploymentSteps.svelte index e58737260..192c70f39 100644 --- a/packages/ui-components/src/lib/components/deployment/DeploymentSteps.svelte +++ b/packages/ui-components/src/lib/components/deployment/DeploymentSteps.svelte @@ -26,6 +26,7 @@ import { page } from '$app/stores'; import { onMount } from 'svelte'; import ShareChoicesButton from './ShareChoicesButton.svelte'; + enum DeploymentStepErrors { NO_GUI = 'Error loading GUI', NO_STRATEGY = 'No valid strategy exists at this URL', diff --git a/packages/ui-components/src/lib/components/deployment/DeploymentTile.svelte b/packages/ui-components/src/lib/components/deployment/DeploymentTile.svelte index a55243b72..e12661aff 100644 --- a/packages/ui-components/src/lib/components/deployment/DeploymentTile.svelte +++ b/packages/ui-components/src/lib/components/deployment/DeploymentTile.svelte @@ -1,12 +1,24 @@

{name}

diff --git a/packages/ui-components/src/lib/components/deployment/StrategySection.svelte b/packages/ui-components/src/lib/components/deployment/StrategyPage.svelte similarity index 67% rename from packages/ui-components/src/lib/components/deployment/StrategySection.svelte rename to packages/ui-components/src/lib/components/deployment/StrategyPage.svelte index 6fa93ed31..e6c8a6ce9 100644 --- a/packages/ui-components/src/lib/components/deployment/StrategySection.svelte +++ b/packages/ui-components/src/lib/components/deployment/StrategyPage.svelte @@ -6,9 +6,8 @@ export let strategyUrl: string = ''; export let strategyName: string = ''; - export let rawDotrain: string = ''; + export let dotrain: string = ''; let strategyDetails: NameAndDescription; - let dotrain: string; let error: string; let errorDetails: string; let markdownContent: string = ''; @@ -29,34 +28,23 @@ const getStrategy = async () => { try { - if (rawDotrain) { - dotrain = rawDotrain; - } else { - const response = await fetch(strategyUrl); - dotrain = await response.text(); - } - try { - strategyDetails = await DotrainOrderGui.getStrategyDetails(dotrain); - if (strategyDetails.description && isMarkdownUrl(strategyDetails.description)) { - const content = await fetchMarkdownContent(strategyDetails.description); - if (content) { - markdownContent = content; - } else { - error = 'Error fetching markdown'; - errorDetails = 'Failed to fetch markdown content'; - } + strategyDetails = await DotrainOrderGui.getStrategyDetails(dotrain); + if (strategyDetails.description && isMarkdownUrl(strategyDetails.description)) { + const content = await fetchMarkdownContent(strategyDetails.description); + if (content) { + markdownContent = content; + } else { + error = 'Error fetching markdown'; + errorDetails = 'Failed to fetch markdown content'; } - } catch (e: unknown) { - error = 'Error getting strategy details'; - errorDetails = e instanceof Error ? e.message : 'Unknown error'; } } catch (e: unknown) { - error = 'Error fetching strategy'; + error = 'Error getting strategy details'; errorDetails = e instanceof Error ? e.message : 'Unknown error'; } }; - $: getStrategy(); + getStrategy(); {#if dotrain && strategyDetails} @@ -79,7 +67,10 @@

{/if}

- +
+

Deployments

+ +
{:else if error} diff --git a/packages/ui-components/src/lib/components/deployment/StrategyShortTile.svelte b/packages/ui-components/src/lib/components/deployment/StrategyShortTile.svelte new file mode 100644 index 000000000..92a2575f9 --- /dev/null +++ b/packages/ui-components/src/lib/components/deployment/StrategyShortTile.svelte @@ -0,0 +1,24 @@ + + + +

{strategyDetails?.name}

+

{strategyDetails?.short_description}

+
diff --git a/packages/ui-components/src/lib/index.ts b/packages/ui-components/src/lib/index.ts index 36703339d..38baec9eb 100644 --- a/packages/ui-components/src/lib/index.ts +++ b/packages/ui-components/src/lib/index.ts @@ -1,5 +1,3 @@ -import './app.css'; - // Components export { default as CardProperty } from './components/CardProperty.svelte'; export { default as Hash, HashType } from './components/Hash.svelte'; @@ -59,10 +57,11 @@ export { default as CodeMirrorDotrain } from './components/CodeMirrorDotrain.sve export { default as OrderOrVaultHash } from './components/OrderOrVaultHash.svelte'; export { default as License } from './components/License.svelte'; export { default as ButtonDarkMode } from './components/ButtonDarkMode.svelte'; -export { default as StrategySection } from './components/deployment/StrategySection.svelte'; +export { default as StrategyPage } from './components/deployment/StrategyPage.svelte'; export { default as InputHex } from './components/input/InputHex.svelte'; export { default as InputTokenAmount } from './components/input/InputTokenAmount.svelte'; export { default as WalletConnect } from './components/wallet/WalletConnect.svelte'; +export { default as StrategyShortTile } from './components/deployment/StrategyShortTile.svelte'; //Types export type { AppStoresInterface } from './types/appStores.ts'; @@ -86,7 +85,6 @@ export { prepareHistoricalOrderChartData } from './services/historicalOrderChart export { bigintToFloat } from './utils/number'; // Constants - export { DEFAULT_PAGE_SIZE, DEFAULT_REFRESH_INTERVAL } from './queries/constants'; export { QKEY_VAULTS, diff --git a/packages/ui-components/src/lib/services/index.ts b/packages/ui-components/src/lib/services/index.ts new file mode 100644 index 000000000..b1f3a1e4a --- /dev/null +++ b/packages/ui-components/src/lib/services/index.ts @@ -0,0 +1,2 @@ +export { fetchParseRegistry, fetchRegistryDotrains } from './registry'; +export type { RegistryDotrain, RegistryFile } from './registry'; diff --git a/packages/webapp/src/lib/services/getFileRegistry.ts b/packages/ui-components/src/lib/services/registry.ts similarity index 61% rename from packages/webapp/src/lib/services/getFileRegistry.ts rename to packages/ui-components/src/lib/services/registry.ts index ff7af8293..6fb86ac11 100644 --- a/packages/webapp/src/lib/services/getFileRegistry.ts +++ b/packages/ui-components/src/lib/services/registry.ts @@ -1,3 +1,13 @@ +export type RegistryFile = { + name: string; + url: string; +}; + +export type RegistryDotrain = { + name: string; + dotrain: string; +}; + /** * Fetches and parses a file registry from a given URL. * The registry is expected to be a text file where each line contains a file name and URL separated by a space. @@ -7,11 +17,11 @@ * @throws Will throw an error if the fetch fails, if the response is not ok, or if the registry format is invalid * * @example - * const files = await getFileRegistry('https://example.com/registry'); + * const files = await fetchParseRegistryFile('https://example.com/registry'); * // Returns: [{ name: 'file1', url: 'https://example.com/file1.rain' }, ...] */ -export const getFileRegistry = async (url: string) => { +export const fetchParseRegistry = async (url: string): Promise<{ name: string; url: string }[]> => { try { const response = await fetch(url); if (!response.ok) { @@ -34,6 +44,29 @@ export const getFileRegistry = async (url: string) => { } }; +export const fetchRegistryDotrains = async (url: string): Promise => { + const files = await fetchParseRegistry(url); + const dotrains = await Promise.all( + files.map(async (file) => { + try { + const response = await fetch(file.url); + if (!response.ok) { + throw new Error(`Failed to fetch dotrain for ${file.name}`); + } + const dotrain = await response.text(); + return { name: file.name, dotrain }; + } catch (e) { + throw new Error( + e instanceof Error + ? `Error fetching dotrain for ${file.name}: ${e.message}` + : `Unknown error fetching dotrain for ${file.name}` + ); + } + }) + ); + return dotrains; +}; + if (import.meta.vitest) { const { describe, it, expect, vi } = import.meta.vitest; @@ -47,7 +80,7 @@ file2.js https://example.com/file2.js`; text: () => Promise.resolve(mockResponse) }); - const result = await getFileRegistry('https://example.com/registry'); + const result = await fetchParseRegistry('https://example.com/registry'); expect(result).toEqual([ { name: 'file1.js', url: 'https://example.com/file1.js' }, { name: 'file2.js', url: 'https://example.com/file2.js' } @@ -59,7 +92,7 @@ file2.js https://example.com/file2.js`; ok: false }); - await expect(getFileRegistry('https://example.com/registry')).rejects.toThrow( + await expect(fetchParseRegistry('https://example.com/registry')).rejects.toThrow( 'Failed to fetch registry' ); }); @@ -67,7 +100,7 @@ file2.js https://example.com/file2.js`; it('should handle network errors', async () => { global.fetch = vi.fn().mockRejectedValue(new Error('Network error')); - await expect(getFileRegistry('https://example.com/registry')).rejects.toThrow( + await expect(fetchParseRegistry('https://example.com/registry')).rejects.toThrow( 'Network error' ); }); diff --git a/packages/webapp/src/routes/deploy/+layout.ts b/packages/webapp/src/routes/deploy/+layout.ts index b14f01b44..4f2fd310b 100644 --- a/packages/webapp/src/routes/deploy/+layout.ts +++ b/packages/webapp/src/routes/deploy/+layout.ts @@ -1,8 +1,19 @@ import { REGISTRY_URL } from '$lib/constants'; +import { fetchRegistryDotrains } from '@rainlanguage/ui-components/services'; import type { LayoutLoad } from './$types'; +import { DotrainOrderGui } from '@rainlanguage/orderbook/js_api'; export const load: LayoutLoad = async ({ url }) => { // get the registry url from the url params const registry = url.searchParams.get('registry'); - return { registry: registry || REGISTRY_URL }; + + const registryDotrains = await fetchRegistryDotrains(registry || REGISTRY_URL); + const strategyDetails = await Promise.all( + registryDotrains.map(async (registryDotrain) => { + const details = await DotrainOrderGui.getStrategyDetails(registryDotrain.dotrain); + return { ...registryDotrain, details }; + }) + ); + + return { registry: registry || REGISTRY_URL, registryDotrains, strategyDetails }; }; diff --git a/packages/webapp/src/routes/deploy/+page.svelte b/packages/webapp/src/routes/deploy/+page.svelte index ceebf1a7b..154bfc0b1 100644 --- a/packages/webapp/src/routes/deploy/+page.svelte +++ b/packages/webapp/src/routes/deploy/+page.svelte @@ -1,44 +1,12 @@ - - - diff --git a/packages/webapp/src/routes/deploy/[strategyName]/+layout.ts b/packages/webapp/src/routes/deploy/[strategyName]/+layout.ts index b5ae39337..e86af7694 100644 --- a/packages/webapp/src/routes/deploy/[strategyName]/+layout.ts +++ b/packages/webapp/src/routes/deploy/[strategyName]/+layout.ts @@ -2,11 +2,10 @@ import { rawDotrain } from '$lib/stores/raw-dotrain'; import { get } from 'svelte/store'; import type { LayoutLoad } from './$types'; import { redirect } from '@sveltejs/kit'; -import { getFileRegistry } from '$lib/services/getFileRegistry'; -export const load: LayoutLoad = async ({ fetch, params, parent }) => { +export const load: LayoutLoad = async ({ params, parent }) => { const { strategyName } = params; - const { registry } = await parent(); + const { registryDotrains } = await parent(); let dotrain; @@ -14,15 +13,7 @@ export const load: LayoutLoad = async ({ fetch, params, parent }) => { if (strategyName === 'raw' && get(rawDotrain)) { dotrain = get(rawDotrain); } else { - const fileList = await getFileRegistry(registry); - - const strategy = fileList.find((file: { name: string }) => file.name === strategyName); - if (!strategy) { - throw new Error(`Strategy ${strategyName} not found`); - } - - const dotrainResponse = await fetch(strategy.url); - dotrain = await dotrainResponse.text(); + dotrain = registryDotrains.find((dotrain) => dotrain.name === strategyName)?.dotrain; } } catch { throw redirect(307, '/deploy'); diff --git a/packages/webapp/src/routes/deploy/[strategyName]/+page.svelte b/packages/webapp/src/routes/deploy/[strategyName]/+page.svelte index 26dbe5c4b..f9433a798 100644 --- a/packages/webapp/src/routes/deploy/[strategyName]/+page.svelte +++ b/packages/webapp/src/routes/deploy/[strategyName]/+page.svelte @@ -1,8 +1,10 @@ - + + + diff --git a/packages/webapp/src/routes/deploy/[strategyName]/[deploymentKey]/+page.svelte b/packages/webapp/src/routes/deploy/[strategyName]/[deploymentKey]/+page.svelte index 6e5784d62..1a7e0faf3 100644 --- a/packages/webapp/src/routes/deploy/[strategyName]/[deploymentKey]/+page.svelte +++ b/packages/webapp/src/routes/deploy/[strategyName]/[deploymentKey]/+page.svelte @@ -1,10 +1,12 @@ + + + (advancedMode = !advancedMode)}> + {'Advanced Mode'} + + + +
+ {#if advancedMode} +
+
+