Skip to content

Commit

Permalink
Merge pull request #1260 from rainlanguage/02/10/25-Add-getTransactio…
Browse files Browse the repository at this point in the history
…n-wasm-fn

Add getTransaction wasm function, to poll for new txs
  • Loading branch information
hardingjam authored Feb 14, 2025
2 parents 42f0589 + 8b7dec3 commit e776932
Show file tree
Hide file tree
Showing 27 changed files with 398 additions and 53 deletions.
11 changes: 11 additions & 0 deletions crates/js_api/src/gui/select_tokens.rs
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,17 @@ impl DotrainOrderGui {
Ok(())
}

#[wasm_bindgen(js_name = "getNetworkKey")]
pub fn get_network_key(&self) -> Result<String, GuiError> {
let order_key = Deployment::parse_order_key(
self.dotrain_order.dotrain_yaml().documents,
&self.selected_deployment,
)?;
let network_key =
Order::parse_network_key(self.dotrain_order.dotrain_yaml().documents, &order_key)?;
Ok(network_key)
}

#[wasm_bindgen(js_name = "saveSelectToken")]
pub async fn save_select_token(
&mut self,
Expand Down
1 change: 1 addition & 0 deletions crates/js_api/src/subgraph/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ use thiserror::Error;
use wasm_bindgen::{JsError, JsValue};

pub mod order;
pub mod transaction;
pub mod vault;

#[derive(Error, Debug)]
Expand Down
16 changes: 16 additions & 0 deletions crates/js_api/src/subgraph/transaction.rs
Original file line number Diff line number Diff line change
@@ -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_transaction(
url: &str,
tx_hash: &str,
) -> Result<JsValue, OrderbookSubgraphClientError> {
let client = OrderbookSubgraphClient::new(Url::parse(url)?);
let transaction = client.transaction_detail(Id::new(tx_hash)).await?;
Ok(to_value(&transaction)?)
}
16 changes: 16 additions & 0 deletions crates/subgraph/src/orderbook_client.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -25,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)]
Expand Down Expand Up @@ -379,4 +382,17 @@ impl OrderbookSubgraphClient {
}
Ok(all_pages_merged)
}

pub async fn transaction_detail(
&self,
id: Id,
) -> Result<Transaction, OrderbookSubgraphClientError> {
let data = self
.query::<TransactionDetailQuery, IdQueryVariables>(IdQueryVariables { id: &id })
.await?;
let transaction = data
.transaction
.ok_or(OrderbookSubgraphClientError::Empty)?;
Ok(transaction)
}
}
1 change: 1 addition & 0 deletions crates/subgraph/src/types/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
12 changes: 12 additions & 0 deletions crates/subgraph/src/types/transaction.rs
Original file line number Diff line number Diff line change
@@ -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<Transaction>,
}
5 changes: 5 additions & 0 deletions packages/orderbook/test/js_api/gui.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1386,5 +1386,10 @@ ${dotrainWithoutVaultIds}`;
"Missing required field 'tokens' in root"
);
});

it('should get network key', async () => {
const networkKey = gui.getNetworkKey();
assert.equal(networkKey, 'some-network');
});
});
});
31 changes: 31 additions & 0 deletions packages/orderbook/test/js_api/transaction.test.ts
Original file line number Diff line number Diff line change
@@ -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)));
}
});
});
18 changes: 14 additions & 4 deletions packages/ui-components/src/__tests__/DeploymentSteps.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import { DotrainOrderGui, type Scenario } from '@rainlanguage/orderbook/js_api';
import type { ComponentProps } from 'svelte';
import { writable } from 'svelte/store';
import type { AppKit } from '@reown/appkit';
import type { ConfigSource } from '../lib/typeshare/config';
const { mockWagmiConfigStore, mockConnectedStore } = await vi.hoisted(
() => import('../lib/__mocks__/stores')
);
Expand Down Expand Up @@ -622,7 +623,8 @@ describe('DeploymentSteps', () => {
it('shows deployment details when provided', async () => {
(DotrainOrderGui.chooseDeployment as Mock).mockResolvedValue({
getSelectTokens: () => [],
getTokenInfo: vi.fn()
getTokenInfo: vi.fn(),
getNetworkKey: vi.fn()
});

render(DeploymentSteps, {
Expand All @@ -633,6 +635,7 @@ describe('DeploymentSteps', () => {
wagmiConnected: mockConnectedStore,
appKitModal: writable({} as AppKit),
handleDeployModal: vi.fn(),
settings: writable({} as ConfigSource),
handleUpdateGuiState: vi.fn()
}
});
Expand All @@ -646,7 +649,8 @@ describe('DeploymentSteps', () => {
const mockSelectTokens = ['token1', 'token2'];
(DotrainOrderGui.chooseDeployment as Mock).mockResolvedValue({
getSelectTokens: () => mockSelectTokens,
getTokenInfo: vi.fn()
getTokenInfo: vi.fn(),
getNetworkKey: vi.fn()
});

render(DeploymentSteps, {
Expand All @@ -657,6 +661,7 @@ describe('DeploymentSteps', () => {
wagmiConnected: mockConnectedStore,
appKitModal: writable({} as AppKit),
handleDeployModal: vi.fn(),
settings: writable({} as ConfigSource),
handleUpdateGuiState: vi.fn()
}
});
Expand All @@ -682,6 +687,7 @@ describe('DeploymentSteps', () => {
wagmiConnected: mockConnectedStore,
appKitModal: writable({} as AppKit),
handleDeployModal: vi.fn(),
settings: writable({} as ConfigSource),
handleUpdateGuiState: vi.fn()
}
});
Expand All @@ -706,7 +712,8 @@ describe('DeploymentSteps', () => {
deposits: []
}),
getAllFieldDefinitions: () => [],
getTokenInfo: vi.fn()
getTokenInfo: vi.fn(),
getNetworkKey: vi.fn()
});

render(DeploymentSteps, {
Expand All @@ -717,6 +724,7 @@ describe('DeploymentSteps', () => {
wagmiConnected: mockConnectedStore,
appKitModal: writable({} as AppKit),
handleDeployModal: vi.fn(),
settings: writable({} as ConfigSource),
handleUpdateGuiState: vi.fn()
}
});
Expand All @@ -739,7 +747,8 @@ describe('DeploymentSteps', () => {
deposits: []
}),
getAllFieldDefinitions: () => [],
getTokenInfo: vi.fn()
getTokenInfo: vi.fn(),
getNetworkKey: vi.fn()
});

render(DeploymentSteps, {
Expand All @@ -750,6 +759,7 @@ describe('DeploymentSteps', () => {
wagmiConnected: mockConnectedStore,
appKitModal: writable({} as AppKit),
handleDeployModal: vi.fn(),
settings: writable({} as ConfigSource),
handleUpdateGuiState: vi.fn()
}
});
Expand Down
36 changes: 25 additions & 11 deletions packages/ui-components/src/__tests__/LightweightChart.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,14 @@ const setDataMock = vi.fn();
const applyOptionsMock = vi.fn();
const setVisibleRangeMock = vi.fn();
const removeMock = vi.fn();
const updateMock = vi.fn();

vi.mock('lightweight-charts', async (importOriginal) => ({
...((await importOriginal()) as object),
createChart: vi.fn(() => ({
addLineSeries: vi.fn(() => ({
setData: setDataMock
setData: setDataMock,
update: updateMock
})),
remove: removeMock,
applyOptions: applyOptionsMock,
Expand Down Expand Up @@ -96,23 +98,18 @@ test('renders with data correctly', async () => {
});
});

test('updates data correctly when props change', async () => {
test('updates data correctly when new data points are added', async () => {
const title = 'test title';
const emptyMessage = 'empty message';
const loading = false;
const priceSymbol = '$';
const createSeries = (chart: IChartApi) => chart.addLineSeries();

const initialData: { value: number; time: UTCTimestamp; color?: string }[] = [
const initialData = [
{ value: 10, time: 1529899200 as UTCTimestamp },
{ value: 20, time: 1529899300 as UTCTimestamp }
];

const newData: { value: number; time: UTCTimestamp; color?: string }[] = [
{ value: 15, time: 1529900000 as UTCTimestamp },
{ value: 25, time: 1529900300 as UTCTimestamp }
];

const { component } = render(LightweightChart, {
title,
emptyMessage,
Expand All @@ -123,15 +120,32 @@ test('updates data correctly when props change', async () => {
lightweightChartsTheme: readable({ test: 'test' })
});

// First render should call setData with initial data
await waitFor(() => {
expect(setDataMock).toHaveBeenCalledWith(initialData);
});

// Update data prop
await act(() => component.$set({ data: newData }));
// Add new data points
const newDataPoints = [
...initialData,
{ value: 30, time: 1529899400 as UTCTimestamp },
{ value: 40, time: 1529899500 as UTCTimestamp }
];

// Update with new data that includes additional points
await act(() => component.$set({ data: newDataPoints }));

// Should call update for each new point
await waitFor(() => {
expect(setDataMock).toHaveBeenCalledWith(newData);
expect(updateMock).toHaveBeenCalledTimes(2);
expect(updateMock).toHaveBeenCalledWith({
value: 30,
time: 1529899400 as UTCTimestamp
});
expect(updateMock).toHaveBeenCalledWith({
value: 40,
time: 1529899500 as UTCTimestamp
});
});
});

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@
rpcUrl="https://example.com"
query={orderDetailQuery}
handleDepositOrWithdrawModal={() => {}}
{subgraphUrl}
/>
</svelte:fragment>
</ButtonVaultLink>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,9 @@ import VaultBalanceChart from '../lib/components/charts/VaultBalanceChart.svelte
import type { Vault } from '@rainlanguage/orderbook/js_api';
import { getVaultBalanceChanges } from '@rainlanguage/orderbook/js_api';
import { writable } from 'svelte/store';
import type { ComponentProps } from 'svelte';

type VaultBalanceChartProps = ComponentProps<VaultBalanceChart>;

vi.mock('@rainlanguage/orderbook/js_api', () => ({
getVaultBalanceChanges: vi.fn()
Expand Down Expand Up @@ -42,8 +45,9 @@ test('calls getVaultBalanceChanges with correct arguments', async () => {
props: {
vault: mockVault,
subgraphUrl: 'https://example.com',
lightweightChartsTheme: writable({})
},
lightweightChartsTheme: writable({}),
id: 'vault1'
} as VaultBalanceChartProps,
context: new Map([['$$_queryClient', queryClient]])
});

Expand Down
Loading

0 comments on commit e776932

Please sign in to comment.