diff --git a/crates/cli/src/commands/order/detail.rs b/crates/cli/src/commands/order/detail.rs index 77719c3df..61d1cd946 100644 --- a/crates/cli/src/commands/order/detail.rs +++ b/crates/cli/src/commands/order/detail.rs @@ -136,7 +136,8 @@ mod tests { "from": encode_prefixed(alloy::primitives::Address::random()) } }], - "trades": [] + "trades": [], + "removeEvents": [] } } }) diff --git a/crates/cli/src/commands/order/list.rs b/crates/cli/src/commands/order/list.rs index e5dbd4427..51184b695 100644 --- a/crates/cli/src/commands/order/list.rs +++ b/crates/cli/src/commands/order/list.rs @@ -264,7 +264,8 @@ mod tests { "from": encode_prefixed(alloy::primitives::Address::random()) } }], - "trades": [] + "trades": [], + "removeEvents": [] }] } }) diff --git a/crates/common/src/dotrain_order/calldata.rs b/crates/common/src/dotrain_order/calldata.rs index 72ad88686..ed48a0c05 100644 --- a/crates/common/src/dotrain_order/calldata.rs +++ b/crates/common/src/dotrain_order/calldata.rs @@ -15,9 +15,9 @@ use std::{collections::HashMap, str::FromStr, sync::Arc}; #[cfg_attr(target_family = "wasm", derive(Tsify))] pub struct ApprovalCalldata { #[cfg_attr(target_family = "wasm", tsify(type = "string"))] - token: Address, + pub token: Address, #[cfg_attr(target_family = "wasm", tsify(type = "string"))] - calldata: Bytes, + pub calldata: Bytes, } #[cfg(target_family = "wasm")] impl_all_wasm_traits!(ApprovalCalldata); diff --git a/crates/js_api/src/gui/deposits.rs b/crates/js_api/src/gui/deposits.rs index b8b885e56..c4a291f34 100644 --- a/crates/js_api/src/gui/deposits.rs +++ b/crates/js_api/src/gui/deposits.rs @@ -1,4 +1,5 @@ use super::*; +use rain_orderbook_app_settings::gui::GuiDeposit; #[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Tsify)] pub struct TokenDeposit { @@ -11,17 +12,22 @@ impl_all_wasm_traits!(TokenDeposit); #[wasm_bindgen] impl DotrainOrderGui { + fn get_gui_deposit(&self, key: &str) -> Result { + let deployment = self.get_current_deployment()?; + let gui_deposit = deployment + .deposits + .iter() + .find(|dg| dg.token.as_ref().map_or(false, |t| t.key == *key)) + .ok_or(GuiError::DepositTokenNotFound(key.to_string()))?; + Ok(gui_deposit.clone()) + } + #[wasm_bindgen(js_name = "getDeposits")] pub fn get_deposits(&self) -> Result, GuiError> { - let deployment = self.get_current_deployment()?; self.deposits .iter() .map(|(key, value)| { - let gui_deposit = deployment - .deposits - .iter() - .find(|dg| dg.token.as_ref().map_or(false, |t| t.key == *key)) - .ok_or(GuiError::DepositTokenNotFound(key.clone()))?; + let gui_deposit = self.get_gui_deposit(key)?; let amount: String = if value.is_preset { let index = value .value @@ -54,12 +60,7 @@ impl DotrainOrderGui { #[wasm_bindgen(js_name = "saveDeposit")] pub fn save_deposit(&mut self, token: String, amount: String) -> Result<(), GuiError> { - let deployment = self.get_current_deployment()?; - let gui_deposit = deployment - .deposits - .iter() - .find(|dg| dg.token.as_ref().map_or(false, |t| t.key == token)) - .ok_or(GuiError::DepositTokenNotFound(token.clone()))?; + let gui_deposit = self.get_gui_deposit(&token)?; if amount.is_empty() { self.remove_deposit(token); @@ -94,12 +95,7 @@ impl DotrainOrderGui { #[wasm_bindgen(js_name = "getDepositPresets")] pub fn get_deposit_presets(&self, key: String) -> Result, GuiError> { - let deployment = self.get_current_deployment()?; - let gui_deposit = deployment - .deposits - .iter() - .find(|dg| dg.token.as_ref().map_or(false, |t| t.key == key)) - .ok_or(GuiError::DepositTokenNotFound(key.clone()))?; + let gui_deposit = self.get_gui_deposit(&key)?; Ok(gui_deposit.presets.clone().unwrap_or(vec![])) } diff --git a/crates/js_api/src/gui/order_operations.rs b/crates/js_api/src/gui/order_operations.rs index 4b6438234..b92344932 100644 --- a/crates/js_api/src/gui/order_operations.rs +++ b/crates/js_api/src/gui/order_operations.rs @@ -9,6 +9,14 @@ use rain_orderbook_bindings::OrderBook::multicallCall; use rain_orderbook_common::{deposit::DepositArgs, dotrain_order, transaction::TransactionArgs}; use std::{collections::HashMap, str::FromStr, sync::Arc}; +pub enum CalldataFunction { + Allowance, + Approval, + Deposit, + AddOrder, + DepositAndAddOrder, +} + #[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Tsify)] pub struct TokenAllowance { @@ -53,6 +61,24 @@ impl_all_wasm_traits!(DepositAndAddOrderCalldataResult); pub struct IOVaultIds(HashMap>>); impl_all_wasm_traits!(IOVaultIds); +#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Tsify)] +pub struct ExtendedApprovalCalldata { + pub token: Address, + pub calldata: Bytes, + pub symbol: String, +} +impl_all_wasm_traits!(ExtendedApprovalCalldata); + +#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Tsify)] +#[serde(rename_all = "camelCase")] +pub struct DeploymentTransactionArgs { + approvals: Vec, + deployment_calldata: Bytes, + orderbook_address: Address, + chain_id: u64, +} +impl_all_wasm_traits!(DeploymentTransactionArgs); + #[wasm_bindgen] impl DotrainOrderGui { fn get_orderbook(&self) -> Result, GuiError> { @@ -129,13 +155,32 @@ impl DotrainOrderGui { }) } + fn prepare_calldata_generation( + &mut self, + calldata_function: CalldataFunction, + ) -> Result { + let deployment = self.get_current_deployment()?; + self.check_select_tokens()?; + match calldata_function { + CalldataFunction::Deposit => { + self.populate_vault_ids(&deployment)?; + } + CalldataFunction::AddOrder | CalldataFunction::DepositAndAddOrder => { + self.check_field_values()?; + self.populate_vault_ids(&deployment)?; + self.update_bindings(&deployment)?; + } + _ => {} + } + Ok(self.get_current_deployment()?) + } + /// Check allowances for all inputs and outputs of the order /// /// Returns a vector of [`TokenAllowance`] objects #[wasm_bindgen(js_name = "checkAllowances")] - pub async fn check_allowances(&self, owner: String) -> Result { - let deployment = self.get_current_deployment()?; - self.check_select_tokens()?; + pub async fn check_allowances(&mut self, owner: String) -> Result { + let deployment = self.prepare_calldata_generation(CalldataFunction::Allowance)?; let orderbook = self.get_orderbook()?; let vaults_and_deposits = self.get_vaults_and_deposits(&deployment).await?; @@ -166,11 +211,10 @@ impl DotrainOrderGui { /// Returns a vector of [`ApprovalCalldata`] objects #[wasm_bindgen(js_name = "generateApprovalCalldatas")] pub async fn generate_approval_calldatas( - &self, + &mut self, owner: String, ) -> Result { - let deployment = self.get_current_deployment()?; - self.check_select_tokens()?; + let deployment = self.prepare_calldata_generation(CalldataFunction::Approval)?; let deposits_map = self.get_deposits_as_map().await?; if deposits_map.is_empty() { @@ -210,10 +254,7 @@ impl DotrainOrderGui { /// Returns a vector of bytes #[wasm_bindgen(js_name = "generateDepositCalldatas")] pub async fn generate_deposit_calldatas(&mut self) -> Result { - let deployment = self.get_current_deployment()?; - self.check_select_tokens()?; - self.populate_vault_ids(&deployment)?; - let deployment = self.get_current_deployment()?; + let deployment = self.prepare_calldata_generation(CalldataFunction::Deposit)?; let token_deposits = self .get_vaults_and_deposits(&deployment) @@ -251,13 +292,7 @@ impl DotrainOrderGui { pub async fn generate_add_order_calldata( &mut self, ) -> Result { - let deployment = self.get_current_deployment()?; - self.check_select_tokens()?; - self.check_field_values()?; - self.populate_vault_ids(&deployment)?; - self.update_bindings(&deployment)?; - let deployment = self.get_current_deployment()?; - + let deployment = self.prepare_calldata_generation(CalldataFunction::AddOrder)?; let calldata = self .dotrain_order .generate_add_order_calldata(&deployment.key) @@ -269,12 +304,7 @@ impl DotrainOrderGui { pub async fn generate_deposit_and_add_order_calldatas( &mut self, ) -> Result { - let deployment = self.get_current_deployment()?; - self.check_select_tokens()?; - self.check_field_values()?; - self.populate_vault_ids(&deployment)?; - self.update_bindings(&deployment)?; - let deployment = self.get_current_deployment()?; + let deployment = self.prepare_calldata_generation(CalldataFunction::DepositAndAddOrder)?; let mut calls = Vec::new(); @@ -356,4 +386,56 @@ impl DotrainOrderGui { self.update_bindings(&deployment)?; Ok(()) } + + #[wasm_bindgen(js_name = "getDeploymentTransactionArgs")] + pub async fn get_deployment_transaction_args( + &mut self, + owner: String, + ) -> Result { + let deployment = self.prepare_calldata_generation(CalldataFunction::DepositAndAddOrder)?; + + let mut approvals = Vec::new(); + let approval_calldata = self.generate_approval_calldatas(owner).await?; + match approval_calldata { + ApprovalCalldataResult::Calldatas(calldatas) => { + let mut output_token_infos = HashMap::new(); + for output in deployment.deployment.order.outputs.clone() { + if output.token.is_none() { + return Err(GuiError::SelectTokensNotSet); + } + let token = output.token.as_ref().unwrap(); + let token_info = self.get_token_info(token.key.clone()).await?; + output_token_infos.insert(token.address.clone(), token_info); + } + + for calldata in calldatas.iter() { + let token_info = output_token_infos + .get(&calldata.token) + .ok_or(GuiError::TokenNotFound(calldata.token.to_string()))?; + approvals.push(ExtendedApprovalCalldata { + token: calldata.token, + calldata: calldata.calldata.clone(), + symbol: token_info.symbol.clone(), + }); + } + } + _ => {} + } + + let deposit_and_add_order_calldata = + self.generate_deposit_and_add_order_calldatas().await?; + + Ok(DeploymentTransactionArgs { + approvals, + deployment_calldata: deposit_and_add_order_calldata.0, + orderbook_address: deployment + .deployment + .order + .orderbook + .as_ref() + .ok_or(GuiError::OrderbookNotFound)? + .address, + chain_id: deployment.deployment.order.network.chain_id, + }) + } } diff --git a/crates/js_api/src/subgraph/mod.rs b/crates/js_api/src/subgraph/mod.rs index 7b843a1ba..6441ab039 100644 --- a/crates/js_api/src/subgraph/mod.rs +++ b/crates/js_api/src/subgraph/mod.rs @@ -9,6 +9,7 @@ use wasm_bindgen::{JsError, JsValue}; pub mod add_order; pub mod order; +pub mod remove_order; pub mod transaction; pub mod vault; diff --git a/crates/js_api/src/subgraph/remove_order.rs b/crates/js_api/src/subgraph/remove_order.rs new file mode 100644 index 000000000..472385799 --- /dev/null +++ b/crates/js_api/src/subgraph/remove_order.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 Remove Orders for a given transaction +/// Returns an array of RemoveOrder structs +#[wasm_bindgen(js_name = "getTransactionRemoveOrders")] +pub async fn get_transaction_remove_orders( + url: &str, + tx_hash: &str, +) -> Result { + let client = OrderbookSubgraphClient::new(Url::parse(url)?); + let remove_orders = client.transaction_remove_orders(Id::new(tx_hash)).await?; + Ok(to_value(&remove_orders)?) +} diff --git a/crates/quote/src/cli/mod.rs b/crates/quote/src/cli/mod.rs index a15d252dc..4c3be47f2 100644 --- a/crates/quote/src/cli/mod.rs +++ b/crates/quote/src/cli/mod.rs @@ -378,7 +378,8 @@ mod tests { }], "meta": null, "timestampAdded": "0", - "trades": [] + "trades": [], + "removeEvents": [] }] } }); diff --git a/crates/quote/src/order_quotes.rs b/crates/quote/src/order_quotes.rs index f6d38b52e..683a082cb 100644 --- a/crates/quote/src/order_quotes.rs +++ b/crates/quote/src/order_quotes.rs @@ -317,6 +317,7 @@ amount price: context<3 0>() context<4 0>(); meta: None, timestamp_added: BigInt(0.to_string()), trades: vec![], + remove_events: vec![], }; let result = get_order_quotes(vec![order], None, local_evm.url(), None) diff --git a/crates/quote/src/quote.rs b/crates/quote/src/quote.rs index bc113ea44..16ca5d90a 100644 --- a/crates/quote/src/quote.rs +++ b/crates/quote/src/quote.rs @@ -431,7 +431,8 @@ mod tests { }], "meta": null, "timestampAdded": "0", - "trades": [] + "trades": [], + "removeEvents": [] }); let retrun_sg_data = if batch { json!({ diff --git a/crates/subgraph/src/orderbook_client.rs b/crates/subgraph/src/orderbook_client.rs index c7096bc9f..b6d09b36b 100644 --- a/crates/subgraph/src/orderbook_client.rs +++ b/crates/subgraph/src/orderbook_client.rs @@ -9,6 +9,7 @@ use crate::types::order::{ OrdersListQuery, }; use crate::types::order_trade::{OrderTradeDetailQuery, OrderTradesListQuery}; +use crate::types::remove_order::{TransactionRemoveOrdersQuery, TransactionRemoveOrdersVariables}; use crate::types::transaction::TransactionDetailQuery; use crate::types::vault::{VaultDetailQuery, VaultsListQuery}; use crate::vault_balance_changes_query::VaultBalanceChangesListPageQueryClient; @@ -416,4 +417,24 @@ impl OrderbookSubgraphClient { Ok(data.add_orders) } + + /// Fetch all remove orders for a given transaction + pub async fn transaction_remove_orders( + &self, + id: Id, + ) -> Result, OrderbookSubgraphClientError> { + let data = self + .query::( + TransactionRemoveOrdersVariables { + id: Bytes(id.inner().to_string()), + }, + ) + .await?; + + if data.remove_orders.is_empty() { + return Err(OrderbookSubgraphClientError::Empty); + } + + Ok(data.remove_orders) + } } diff --git a/crates/subgraph/src/performance/order_performance.rs b/crates/subgraph/src/performance/order_performance.rs index 3a366b1a9..39717dc57 100644 --- a/crates/subgraph/src/performance/order_performance.rs +++ b/crates/subgraph/src/performance/order_performance.rs @@ -824,6 +824,7 @@ mod test { meta: None, add_events: vec![], trades: vec![], + remove_events: vec![], } } diff --git a/crates/subgraph/src/types/common.rs b/crates/subgraph/src/types/common.rs index 18404311d..39da8c964 100644 --- a/crates/subgraph/src/types/common.rs +++ b/crates/subgraph/src/types/common.rs @@ -97,6 +97,7 @@ pub struct Order { pub meta: Option, pub add_events: Vec, pub trades: Vec, + pub remove_events: Vec, } #[derive(Debug, Serialize, Deserialize, Clone)] @@ -381,6 +382,12 @@ pub struct Transaction { pub struct AddOrder { pub transaction: Transaction, } +#[derive(cynic::QueryFragment, Debug, Serialize, Clone)] +#[cfg_attr(target_family = "wasm", derive(Tsify))] +#[typeshare] +pub struct RemoveOrder { + pub transaction: Transaction, +} #[derive(cynic::QueryFragment, Debug, Serialize, Clone)] #[cfg_attr(target_family = "wasm", derive(Tsify))] @@ -391,6 +398,15 @@ pub struct AddOrderWithOrder { pub order: Order, } +#[derive(cynic::QueryFragment, Debug, Serialize, Clone)] +#[cfg_attr(target_family = "wasm", derive(Tsify))] +#[cynic(graphql_type = "RemoveOrder")] +pub struct RemoveOrderWithOrder { + pub transaction: Transaction, + #[cfg_attr(target_family = "wasm", tsify(type = "OrderSubgraph"))] + pub order: Order, +} + #[derive(cynic::Scalar, Debug, Clone, PartialEq, Eq, Hash)] #[cfg_attr(target_family = "wasm", derive(Tsify), serde(rename = "SgBigInt"))] #[typeshare] diff --git a/crates/subgraph/src/types/mod.rs b/crates/subgraph/src/types/mod.rs index 59d6e66af..72b169a62 100644 --- a/crates/subgraph/src/types/mod.rs +++ b/crates/subgraph/src/types/mod.rs @@ -4,6 +4,7 @@ mod impls; pub mod order; pub mod order_detail_traits; pub mod order_trade; +pub mod remove_order; pub mod transaction; pub mod vault; diff --git a/crates/subgraph/src/types/order_detail_traits.rs b/crates/subgraph/src/types/order_detail_traits.rs index dc6e5e8af..92e783de1 100644 --- a/crates/subgraph/src/types/order_detail_traits.rs +++ b/crates/subgraph/src/types/order_detail_traits.rs @@ -82,7 +82,8 @@ mod tests { }, // Only the order_bytes field is used for the conversion order_bytes: Bytes("0x00000000000000000000000000000000000000000000000000000000000000200000000000000000000000006171c21b2e553c59a64d1337211b77c367cefe5d00000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000000001c000000000000000000000000000000000000000000000000000000000000002400000000000000000000000000000000000000000000000000000000000000001000000000000000000000000379b966dc6b117dd47b5fc5308534256a4ab1bcc0000000000000000000000006e4b01603edbda617002a077420e98c86595748e000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000950000000000000000000000000000000000000000000000000000000000000002ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0000000000000000000000000000000000000000000000000b1a2bc2ec5000000000000000000000000000000000000000000000000000000000000000000015020000000c020200020110000001100001000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000100000000000000000000000050c5725949a6f0c72e6c4a641f24049a917db0cb000000000000000000000000000000000000000000000000000000000000001200000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000001000000000000000000000000833589fcd6edb6e08f4c7c32d4f71b54bda0291300000000000000000000000000000000000000000000000000000000000000060000000000000000000000000000000000000000000000000000000000000001".into()), - trades: vec![] + trades: vec![], + remove_events: vec![], }; let order_v3: OrderV3 = order_detail.try_into().unwrap(); diff --git a/crates/subgraph/src/types/remove_order.rs b/crates/subgraph/src/types/remove_order.rs new file mode 100644 index 000000000..7359ca178 --- /dev/null +++ b/crates/subgraph/src/types/remove_order.rs @@ -0,0 +1,16 @@ +use super::common::{Bytes, RemoveOrderWithOrder}; +use crate::schema; +use typeshare::typeshare; + +#[derive(cynic::QueryVariables, Debug)] +pub struct TransactionRemoveOrdersVariables { + pub id: Bytes, +} + +#[derive(cynic::QueryFragment, Debug)] +#[cynic(graphql_type = "Query", variables = "TransactionRemoveOrdersVariables")] +#[typeshare] +pub struct TransactionRemoveOrdersQuery { + #[arguments(where: { transaction_: { id: $id } })] + pub remove_orders: Vec, +} diff --git a/crates/subgraph/tests/snapshots/batch_order_test__batch_order_query_gql_output.snap b/crates/subgraph/tests/snapshots/batch_order_test__batch_order_query_gql_output.snap index b02666f22..f5f249bd6 100644 --- a/crates/subgraph/tests/snapshots/batch_order_test__batch_order_query_gql_output.snap +++ b/crates/subgraph/tests/snapshots/batch_order_test__batch_order_query_gql_output.snap @@ -309,5 +309,13 @@ query BatchOrderDetailQuery($id_list: Order_filter!) { trades { id } + removeEvents { + transaction { + id + from + blockNumber + timestamp + } + } } } diff --git a/crates/subgraph/tests/snapshots/order_test__orders_query_gql_output.snap b/crates/subgraph/tests/snapshots/order_test__orders_query_gql_output.snap index 754ed1164..84b3f0b88 100644 --- a/crates/subgraph/tests/snapshots/order_test__orders_query_gql_output.snap +++ b/crates/subgraph/tests/snapshots/order_test__orders_query_gql_output.snap @@ -1,5 +1,6 @@ --- source: crates/subgraph/tests/order_test.rs +assertion_line: 13 expression: request_body.query --- query OrderDetailQuery($id: ID!) { @@ -309,5 +310,13 @@ query OrderDetailQuery($id: ID!) { trades { id } + removeEvents { + transaction { + id + from + blockNumber + timestamp + } + } } } diff --git a/crates/subgraph/tests/snapshots/orders_test__orders_query_gql_output.snap b/crates/subgraph/tests/snapshots/orders_test__orders_query_gql_output.snap index 7e6dd6dcb..9dca63e17 100644 --- a/crates/subgraph/tests/snapshots/orders_test__orders_query_gql_output.snap +++ b/crates/subgraph/tests/snapshots/orders_test__orders_query_gql_output.snap @@ -1,5 +1,6 @@ --- source: crates/subgraph/tests/orders_test.rs +assertion_line: 15 expression: request_body.query --- query OrdersListQuery($first: Int, $skip: Int, $filters: Order_filter) { @@ -309,5 +310,13 @@ query OrdersListQuery($first: Int, $skip: Int, $filters: Order_filter) { trades { id } + removeEvents { + transaction { + id + from + blockNumber + timestamp + } + } } } diff --git a/packages/orderbook/test/js_api/addOrder.test.ts b/packages/orderbook/test/js_api/addOrder.test.ts index 5bcc1cc3c..49cda9e52 100644 --- a/packages/orderbook/test/js_api/addOrder.test.ts +++ b/packages/orderbook/test/js_api/addOrder.test.ts @@ -352,7 +352,8 @@ const mockAddOrder: AddOrderWithOrder = { } } ], - trades: [] + trades: [], + removeEvents: [] } }; diff --git a/packages/orderbook/test/js_api/gui.test.ts b/packages/orderbook/test/js_api/gui.test.ts index d270a594a..88b1b19f7 100644 --- a/packages/orderbook/test/js_api/gui.test.ts +++ b/packages/orderbook/test/js_api/gui.test.ts @@ -7,6 +7,7 @@ import { AllowancesResult, DeploymentDetails, DeploymentKeys, + DeploymentTransactionArgs, DepositAndAddOrderCalldataResult, Gui, GuiDeployment, @@ -1235,6 +1236,60 @@ ${dotrainWithoutVaultIds}`; const calldatas = await gui.generateDepositCalldatas(); assert.equal(calldatas.Calldatas.length, 0); }); + + it('should generate deployment transaction args', async () => { + await mockServer + .forPost('/rpc-url') + .thenSendJsonRpcResult( + '0x00000000000000000000000000000000000000000000003635C9ADC5DEA00000' + ); + await mockServer + .forPost('/rpc-url') + .withBodyIncluding('0xf0cfdd37') + .thenSendJsonRpcResult(`0x${'0'.repeat(24) + '1'.repeat(40)}`); + await mockServer + .forPost('/rpc-url') + .withBodyIncluding('0xc19423bc') + .thenSendJsonRpcResult(`0x${'0'.repeat(24) + '2'.repeat(40)}`); + await mockServer + .forPost('/rpc-url') + .withBodyIncluding('0x24376855') + .thenSendJsonRpcResult(`0x${'0'.repeat(24) + '3'.repeat(40)}`); + await mockServer + .forPost('/rpc-url') + .withBodyIncluding('0xa3869e14') + .thenSendJsonRpcResult( + '0x000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000021234000000000000000000000000000000000000000000000000000000000000' + ); + + gui.saveDeposit('token2', '5000'); + gui.saveFieldValue('test-binding', { + isPreset: false, + value: '10' + }); + + let result: DeploymentTransactionArgs = await gui.getDeploymentTransactionArgs( + '0x1234567890abcdef1234567890abcdef12345678' + ); + + assert.equal(result.approvals.length, 1); + assert.equal( + result.approvals[0].calldata, + '0x095ea7b3000000000000000000000000c95a5f8efe14d7a20bd2e5bafec4e71f8ce0b9a600000000000000000000000000000000000000000000010f0cf064dd59200000' + ); + assert.equal(result.approvals[0].symbol, 'T2'); + assert.equal(result.deploymentCalldata.length, 3146); + assert.equal(result.orderbookAddress, '0xc95a5f8efe14d7a20bd2e5bafec4e71f8ce0b9a6'); + assert.equal(result.chainId, 123); + + gui.removeDeposit('token2'); + result = await gui.getDeploymentTransactionArgs('0x1234567890abcdef1234567890abcdef12345678'); + + assert.equal(result.approvals.length, 0); + assert.equal(result.deploymentCalldata.length, 2634); + assert.equal(result.orderbookAddress, '0xc95a5f8efe14d7a20bd2e5bafec4e71f8ce0b9a6'); + assert.equal(result.chainId, 123); + }); }); describe('select tokens tests', async () => { diff --git a/packages/orderbook/test/js_api/order.test.ts b/packages/orderbook/test/js_api/order.test.ts index dda2643c0..e7065cea9 100644 --- a/packages/orderbook/test/js_api/order.test.ts +++ b/packages/orderbook/test/js_api/order.test.ts @@ -81,7 +81,8 @@ const order1 = { orderbook: { id: '0x0000000000000000000000000000000000000000' }, - trades: [] + trades: [], + removeEvents: [] }; const order2 = { id: 'order2', @@ -147,7 +148,8 @@ const order2 = { orderbook: { id: '0x0000000000000000000000000000000000000000' }, - trades: [] + trades: [], + removeEvents: [] } as unknown as OrderSubgraph; export const order3 = { @@ -214,7 +216,8 @@ export const order3 = { orderbook: { id: '0x0000000000000000000000000000000000000000' }, - trades: [] + trades: [], + removeEvents: [] }; const mockOrderTradesList: Trade[] = [ diff --git a/packages/orderbook/test/js_api/removeOrder.test.ts b/packages/orderbook/test/js_api/removeOrder.test.ts new file mode 100644 index 000000000..a216f0c76 --- /dev/null +++ b/packages/orderbook/test/js_api/removeOrder.test.ts @@ -0,0 +1,144 @@ +import assert from 'assert'; +import { getLocal } from 'mockttp'; +import { describe, it, beforeEach, afterEach } from 'vitest'; +import { Transaction, RemoveOrderWithOrder } from '../../dist/types/js_api.js'; +import { getTransactionRemoveOrders } from '../../dist/cjs/js_api.js'; + +const transaction1: Transaction = { + id: '0x0da3659c0fd5258e962bf339afeaffddb06cc7a473802228b9586fe7503ed13a', + from: '0xf08bcbce72f62c95dcb7c07dcb5ed26acfcfbc11', + blockNumber: '37623990', + timestamp: '1739815758' +}; + +const mockRemoveOrder: RemoveOrderWithOrder = { + transaction: { + id: '0x0da3659c0fd5258e962bf339afeaffddb06cc7a473802228b9586fe7503ed13a', + from: '0xf08bcbce72f62c95dcb7c07dcb5ed26acfcfbc11', + blockNumber: '37623990', + timestamp: '1739815758' + }, + order: { + id: '0xd1639ec740f1fcfa7ca4aac827df554a03e88a36f13818d08ed77863f1be8177', + orderBytes: + '0x0000000000000000000000000000000000000000000000000000000000000020000000000000000000000000f08bcbce72f62c95dcb7c07dcb5ed26acfcfbc1100000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000000001e00000000000000000000000000000000000000000000000000000000000000260a815e3d897b3376629372a3afc69a320a611d52f3cf914335b9bc31021eec2990000000000000000000000005fb33d710f8b58de4c9fdec703b5c2487a5219d600000000000000000000000084c6e7f5a1e5dd89594cc25bef4722a1b8871ae6000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000ad00000000000000000000000000000000000000000000000000000000000000020000000000000000000000001d80c49bbbcd1c0911346656b529df9e5c2f783d0000000000000000000000000000000000000000000000000de0b6b3a7640000000000000000000000000000000000000000000000000000000000000000002d0200000024080500021810000001100001361100000110000101100000031000041e12000022130000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000010000000000000000000000001d80c49bbbcd1c0911346656b529df9e5c2f783d0000000000000000000000000000000000000000000000000000000000000012d863ddba8cdc7d7c413aa97726cfab247fe88490a271785ae7bfee35fdc4765600000000000000000000000000000000000000000000000000000000000000010000000000000000000000001d80c49bbbcd1c0911346656b529df9e5c2f783d0000000000000000000000000000000000000000000000000000000000000012d863ddba8cdc7d7c413aa97726cfab247fe88490a271785ae7bfee35fdc47656', + orderHash: '0xb0d70b12a2ddb9fd96b5a5f20d778c4adf81d5c9c9b7755b7ca2f015545f9077', + owner: '0xf08bcbce72f62c95dcb7c07dcb5ed26acfcfbc11', + outputs: [ + { + id: '0x62505c1dc17df48ee33b6365accc6f022e04a56ec326ed94ad42d6af2e1e2cc7', + owner: '0xf08bcbce72f62c95dcb7c07dcb5ed26acfcfbc11', + vaultId: '97876023468725745973349024037907513632727724269320958133054700715755104925270', + balance: '0', + token: { + id: '0x1d80c49bbbcd1c0911346656b529df9e5c2f783d', + address: '0x1d80c49bbbcd1c0911346656b529df9e5c2f783d', + name: 'Wrapped Flare', + symbol: 'WFLR', + decimals: '18' + }, + orderbook: { + id: '0xcee8cd002f151a536394e564b84076c41bbbcd4d' + }, + ordersAsOutput: [ + { + id: '0xd1639ec740f1fcfa7ca4aac827df554a03e88a36f13818d08ed77863f1be8177', + orderHash: '0xb0d70b12a2ddb9fd96b5a5f20d778c4adf81d5c9c9b7755b7ca2f015545f9077', + active: false + } + ], + ordersAsInput: [ + { + id: '0xd1639ec740f1fcfa7ca4aac827df554a03e88a36f13818d08ed77863f1be8177', + orderHash: '0xb0d70b12a2ddb9fd96b5a5f20d778c4adf81d5c9c9b7755b7ca2f015545f9077', + active: false + } + ], + balanceChanges: [] + } + ], + inputs: [ + { + id: '0x62505c1dc17df48ee33b6365accc6f022e04a56ec326ed94ad42d6af2e1e2cc7', + owner: '0xf08bcbce72f62c95dcb7c07dcb5ed26acfcfbc11', + vaultId: '97876023468725745973349024037907513632727724269320958133054700715755104925270', + balance: '0', + token: { + id: '0x1d80c49bbbcd1c0911346656b529df9e5c2f783d', + address: '0x1d80c49bbbcd1c0911346656b529df9e5c2f783d', + name: 'Wrapped Flare', + symbol: 'WFLR', + decimals: '18' + }, + orderbook: { + id: '0xcee8cd002f151a536394e564b84076c41bbbcd4d' + }, + ordersAsOutput: [ + { + id: '0xd1639ec740f1fcfa7ca4aac827df554a03e88a36f13818d08ed77863f1be8177', + orderHash: '0xb0d70b12a2ddb9fd96b5a5f20d778c4adf81d5c9c9b7755b7ca2f015545f9077', + active: false + } + ], + ordersAsInput: [ + { + id: '0xd1639ec740f1fcfa7ca4aac827df554a03e88a36f13818d08ed77863f1be8177', + orderHash: '0xb0d70b12a2ddb9fd96b5a5f20d778c4adf81d5c9c9b7755b7ca2f015545f9077', + active: false + } + ], + balanceChanges: [] + } + ], + orderbook: { + id: '0xcee8cd002f151a536394e564b84076c41bbbcd4d' + }, + active: false, + timestampAdded: '1739813495', + meta: '0xff0a89c674ee7874a30058ed2f2a20302e2063616c63756c6174652d696f202a2f200a7573696e672d776f7264732d66726f6d203078466532343131434461313933443945346538334135633233344337466433323031303138383361430a6d61782d6f75747075743a206d61782d76616c756528292c0a696f3a206966280a2020657175616c2d746f280a202020206f75747075742d746f6b656e28290a202020203078316438306334396262626364316330393131333436363536623532396466396535633266373833640a2020290a2020310a2020696e762831290a293b0a0a2f2a20312e2068616e646c652d696f202a2f200a3a3b011bff13109e41336ff20278186170706c69636174696f6e2f6f637465742d73747265616d', + addEvents: [ + { + transaction: { + id: '0xea3caf78e023487df10792f0a86d6988e165ff514870ef66f31a1c928874c982', + from: '0xf08bcbce72f62c95dcb7c07dcb5ed26acfcfbc11', + blockNumber: '37622839', + timestamp: '1739813495' + } + } + ], + trades: [], + removeEvents: [ + { + transaction: { + id: '0x0da3659c0fd5258e962bf339afeaffddb06cc7a473802228b9586fe7503ed13a', + from: '0xf08bcbce72f62c95dcb7c07dcb5ed26acfcfbc11', + blockNumber: '37623990', + timestamp: '1739815758' + } + } + ] + } +}; + +const removeOrders: RemoveOrderWithOrder[] = [mockRemoveOrder]; + +describe('Rain Orderbook JS API Package Bindgen Tests - Remove Order', async function () { + const mockServer = getLocal(); + beforeEach(() => mockServer.start(8099)); + afterEach(() => mockServer.stop()); + + it('should fetch remove orders for a transaction', async () => { + await mockServer + .forPost('/sg1') + .thenReply(200, JSON.stringify({ data: { removeOrders: removeOrders } })); + try { + const result: RemoveOrderWithOrder[] = await getTransactionRemoveOrders( + mockServer.url + '/sg1', + transaction1.id + ); + assert.equal(result[0].order.id, mockRemoveOrder.order.id); + } catch (e) { + assert.fail('expected to resolve, but failed' + (e instanceof Error ? e.message : String(e))); + } + }); +}); diff --git a/packages/orderbook/test/js_api/vault.test.ts b/packages/orderbook/test/js_api/vault.test.ts index fe8e53a72..a268d0f94 100644 --- a/packages/orderbook/test/js_api/vault.test.ts +++ b/packages/orderbook/test/js_api/vault.test.ts @@ -231,7 +231,8 @@ describe('Rain Orderbook JS API Package Bindgen Vault Tests', async function () orderbook: { id: '0x0000000000000000000000000000000000000000' }, - trades: [] + trades: [], + removeEvents: [] }; it('should get deposit calldata for a vault', async () => { diff --git a/packages/orderbook/test/quote/test.test.ts b/packages/orderbook/test/quote/test.test.ts index a45d0ebbc..a7558762e 100644 --- a/packages/orderbook/test/quote/test.test.ts +++ b/packages/orderbook/test/quote/test.test.ts @@ -5,8 +5,7 @@ import { QuoteSpec, QuoteTarget, OrderQuoteValue, - BatchOrderQuotesResponse, - Order + BatchOrderQuotesResponse } from '../../dist/types/quote'; import { getId, @@ -14,6 +13,7 @@ import { getQuoteTargetFromSubgraph, getOrderQuote } from '../../dist/cjs/quote'; +import { OrderSubgraph } from '../../dist/types/js_api'; describe('Rain Orderbook Quote Package Bindgen Tests', async function () { const mockServer = getLocal(); @@ -98,7 +98,8 @@ describe('Rain Orderbook Quote Package Bindgen Tests', async function () { orderbook: { id: '0x0000000000000000000000000000000000000000' }, - trades: [] + trades: [], + removeEvents: [] } ] } @@ -208,7 +209,7 @@ describe('Rain Orderbook Quote Package Bindgen Tests', async function () { .thenSendJsonRpcResult( '0x000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000060000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000002' ); - const order: Order = { + const order = { id: '0x46891c626a8a188610b902ee4a0ce8a7e81915e1b922584f8168d14525899dfb', orderBytes: '0x0000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000a0000000000000000000000000000000000000000000000000000000000000012000000000000000000000000000000000000000000000000000000000000001a00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000060000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000010000000000000000000000001111111111111111111111111111111111111111000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001000000000000000000000000222222222222222222222222222222222222222200000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000', @@ -272,8 +273,9 @@ describe('Rain Orderbook Quote Package Bindgen Tests', async function () { orderbook: { id: '0x0000000000000000000000000000000000000000' }, - trades: [] - }; + trades: [], + removeEvents: [] + } as unknown as OrderSubgraph; const result = await getOrderQuote([order], mockServer.url + '/rpc-url'); const expected: BatchOrderQuotesResponse[] = [ { diff --git a/packages/ui-components/src/__tests__/OrderDetail.test.svelte b/packages/ui-components/src/__tests__/OrderDetail.test.svelte index b779e0b72..db5b7d727 100644 --- a/packages/ui-components/src/__tests__/OrderDetail.test.svelte +++ b/packages/ui-components/src/__tests__/OrderDetail.test.svelte @@ -42,7 +42,8 @@ order: data.order, onRemove: $orderDetailQuery.refetch, chainId, - orderbookAddress + orderbookAddress, + subgraphUrl } })} disabled={!handleOrderRemoveModal} diff --git a/packages/ui-components/src/__tests__/getDeploymentTransactionArgs.test.ts b/packages/ui-components/src/__tests__/getDeploymentTransactionArgs.test.ts index a85760ebe..29144a8a8 100644 --- a/packages/ui-components/src/__tests__/getDeploymentTransactionArgs.test.ts +++ b/packages/ui-components/src/__tests__/getDeploymentTransactionArgs.test.ts @@ -5,7 +5,7 @@ import { } from '../lib/components/deployment/getDeploymentTransactionArgs'; import { getAccount } from '@wagmi/core'; import type { Config } from '@wagmi/core'; -import type { DotrainOrderGui, OrderIO } from '@rainlanguage/orderbook/js_api'; +import type { DotrainOrderGui } from '@rainlanguage/orderbook/js_api'; // Mock wagmi/core vi.mock('@wagmi/core', () => ({ @@ -15,36 +15,22 @@ vi.mock('@wagmi/core', () => ({ describe('getDeploymentTransactionArgs', () => { let mockGui: DotrainOrderGui; let mockWagmiConfig: Config; - let mockTokenOutputs: OrderIO[]; beforeEach(() => { vi.clearAllMocks(); // Mock GUI with successful responses mockGui = { - generateApprovalCalldatas: vi.fn().mockResolvedValue([{ token: '0x123', amount: '1000' }]), - generateDepositAndAddOrderCalldatas: vi.fn().mockResolvedValue({ - deposit: '0xdeposit', - addOrder: '0xaddOrder' - }), - getCurrentDeployment: vi.fn().mockReturnValue({ - deployment: { - order: { - network: { 'chain-id': 1 }, - orderbook: { address: '0xorderbook' } - } - } - }), - getTokenInfo: vi.fn().mockResolvedValue({ - address: '0x123', - symbol: 'TEST' + getDeploymentTransactionArgs: vi.fn().mockResolvedValue({ + chainId: 1, + orderbookAddress: '0xorderbook', + approvals: [{ token: '0x123', calldata: '0x1', symbol: 'TEST' }], + deploymentCalldata: '0x1' }) } as unknown as DotrainOrderGui; mockWagmiConfig = {} as Config; (getAccount as Mock).mockReturnValue({ address: '0xuser' }); - - mockTokenOutputs = [{ token: { key: 'token1' } }] as OrderIO[]; }); describe('successful cases', () => { @@ -52,127 +38,34 @@ describe('getDeploymentTransactionArgs', () => { mockGui.generateApprovalCalldatas = vi.fn().mockResolvedValue({ Calldatas: [{ token: '0x123', amount: '1000' }] }); - const result = await getDeploymentTransactionArgs(mockGui, mockWagmiConfig, mockTokenOutputs); + const result = await getDeploymentTransactionArgs(mockGui, mockWagmiConfig); expect(result).toEqual({ - approvals: [{ token: '0x123', amount: '1000', symbol: 'TEST' }], - deploymentCalldata: { - deposit: '0xdeposit', - addOrder: '0xaddOrder' - }, + approvals: [{ token: '0x123', calldata: '0x1', symbol: 'TEST' }], + deploymentCalldata: '0x1', orderbookAddress: '0xorderbook', chainId: 1 }); - - expect(mockGui.generateApprovalCalldatas).toHaveBeenCalledWith('0xuser'); - expect(mockGui.generateDepositAndAddOrderCalldatas).toHaveBeenCalled(); }); }); describe('input validation errors', () => { it('should throw MISSING_GUI when GUI is null', async () => { - await expect( - getDeploymentTransactionArgs(null, mockWagmiConfig, mockTokenOutputs) - ).rejects.toThrow(AddOrderErrors.MISSING_GUI); + await expect(getDeploymentTransactionArgs(null, mockWagmiConfig)).rejects.toThrow( + AddOrderErrors.MISSING_GUI + ); }); it('should throw MISSING_CONFIG when wagmiConfig is undefined', async () => { - await expect( - getDeploymentTransactionArgs(mockGui, undefined, mockTokenOutputs) - ).rejects.toThrow(AddOrderErrors.MISSING_CONFIG); + await expect(getDeploymentTransactionArgs(mockGui, undefined)).rejects.toThrow( + AddOrderErrors.MISSING_CONFIG + ); }); it('should throw NO_WALLET when wallet address is not found', async () => { (getAccount as Mock).mockReturnValue({ address: null }); - await expect( - getDeploymentTransactionArgs(mockGui, mockWagmiConfig, mockTokenOutputs) - ).rejects.toThrow(AddOrderErrors.NO_WALLET); - }); - }); - - describe('deployment errors', () => { - it('should throw INVALID_CHAIN_ID when chain ID is missing', async () => { - mockGui.getCurrentDeployment = vi.fn().mockReturnValue({ - deployment: { - order: { - network: {}, - orderbook: { address: '0xorderbook' } - } - } - }); - - await expect( - getDeploymentTransactionArgs(mockGui, mockWagmiConfig, mockTokenOutputs) - ).rejects.toThrow(AddOrderErrors.INVALID_CHAIN_ID); - }); - - it('should throw MISSING_ORDERBOOK when orderbook address is missing', async () => { - mockGui.getCurrentDeployment = vi.fn().mockReturnValue({ - deployment: { - order: { - network: { 'chain-id': 1 }, - orderbook: {} - } - } - }); - - await expect( - getDeploymentTransactionArgs(mockGui, mockWagmiConfig, mockTokenOutputs) - ).rejects.toThrow(AddOrderErrors.MISSING_ORDERBOOK); - }); - }); - - describe('approval and calldata errors', () => { - it('should throw APPROVAL_FAILED when generateApprovalCalldatas fails', async () => { - mockGui.generateApprovalCalldatas = vi.fn().mockRejectedValue(new Error('Approval error')); - - await expect( - getDeploymentTransactionArgs(mockGui, mockWagmiConfig, mockTokenOutputs) - ).rejects.toThrow(`${AddOrderErrors.APPROVAL_FAILED}: Approval error`); - }); - - it('should throw DEPLOYMENT_FAILED when generateDepositAndAddOrderCalldatas fails', async () => { - mockGui.generateDepositAndAddOrderCalldatas = vi - .fn() - .mockRejectedValue(new Error('Deployment error')); - - await expect( - getDeploymentTransactionArgs(mockGui, mockWagmiConfig, mockTokenOutputs) - ).rejects.toThrow(`${AddOrderErrors.DEPLOYMENT_FAILED}: Deployment error`); - }); - }); - - describe('token info errors', () => { - it('should throw TOKEN_INFO_FAILED when token key is missing', async () => { - const invalidTokenOutputs = [{ token: {} }] as OrderIO[]; - - await expect( - getDeploymentTransactionArgs(mockGui, mockWagmiConfig, invalidTokenOutputs) - ).rejects.toThrow(`${AddOrderErrors.TOKEN_INFO_FAILED}: Token key is missing`); - }); - - it('should throw TOKEN_INFO_FAILED when getTokenInfo fails', async () => { - mockGui.getTokenInfo = vi.fn().mockRejectedValue(new Error('Token info error')); - - await expect( - getDeploymentTransactionArgs(mockGui, mockWagmiConfig, mockTokenOutputs) - ).rejects.toThrow(`${AddOrderErrors.TOKEN_INFO_FAILED}: Token info error`); - }); - - it('should throw TOKEN_INFO_FAILED when token info is not found for approval', async () => { - mockGui.getTokenInfo = vi.fn().mockResolvedValue({ - address: '0x456', // Different address than the approval token - symbol: 'TEST' - }); - - mockGui.generateApprovalCalldatas = vi.fn().mockResolvedValue({ - Calldatas: [{ token: '0x123', amount: '1000' }] - }); - - await expect( - getDeploymentTransactionArgs(mockGui, mockWagmiConfig, mockTokenOutputs) - ).rejects.toThrow( - `${AddOrderErrors.TOKEN_INFO_FAILED}: Token info not found for address: 0x123` + await expect(getDeploymentTransactionArgs(mockGui, mockWagmiConfig)).rejects.toThrow( + AddOrderErrors.NO_WALLET ); }); }); diff --git a/packages/ui-components/src/__tests__/transactionStore.test.ts b/packages/ui-components/src/__tests__/transactionStore.test.ts index 2d9e7843e..a466c4080 100644 --- a/packages/ui-components/src/__tests__/transactionStore.test.ts +++ b/packages/ui-components/src/__tests__/transactionStore.test.ts @@ -5,7 +5,11 @@ import transactionStore, { TransactionErrorMessage } from '../lib/stores/transactionStore'; import { waitForTransactionReceipt, sendTransaction, switchChain, type Config } from '@wagmi/core'; -import { getTransaction, getTransactionAddOrders } from '@rainlanguage/orderbook/js_api'; +import { + getTransaction, + getTransactionAddOrders, + getTransactionRemoveOrders +} from '@rainlanguage/orderbook/js_api'; import { waitFor } from '@testing-library/svelte'; vi.mock('@wagmi/core', () => ({ @@ -16,7 +20,8 @@ vi.mock('@wagmi/core', () => ({ vi.mock('@rainlanguage/orderbook/js_api', () => ({ getTransaction: vi.fn(), - getTransactionAddOrders: vi.fn() + getTransactionAddOrders: vi.fn(), + getTransactionRemoveOrders: vi.fn() })); describe('transactionStore', () => { @@ -32,7 +37,8 @@ describe('transactionStore', () => { transactionSuccess, transactionError, awaitTransactionIndexing, - awaitNewOrderIndexing + awaitNewOrderIndexing, + awaitRemoveOrderIndexing } = transactionStore; beforeEach(() => { @@ -339,4 +345,52 @@ describe('transactionStore', () => { vi.useRealTimers(); }); + + it('should handle successful remove order indexing', async () => { + const mockSubgraphUrl = 'test.com'; + const mockTxHash = 'mockHash'; + + (getTransactionRemoveOrders as Mock).mockResolvedValue([ + { + order: { + id: 'removedOrder123' + } + } + ]); + + vi.useFakeTimers({ shouldAdvanceTime: true }); + + await awaitRemoveOrderIndexing(mockSubgraphUrl, mockTxHash); + + vi.runOnlyPendingTimers(); + + await waitFor(() => { + expect(get(transactionStore).status).toBe(TransactionStatus.SUCCESS); + expect(get(transactionStore).hash).toBe(mockTxHash); + }); + }); + + it('should handle remove order indexing timeout', async () => { + vi.useFakeTimers(); + const mockSubgraphUrl = 'test.com'; + const mockTxHash = 'mockHash'; + + (getTransactionRemoveOrders as Mock).mockResolvedValue([]); + + const indexingPromise = awaitRemoveOrderIndexing(mockSubgraphUrl, mockTxHash); + + expect(get(transactionStore).status).toBe(TransactionStatus.PENDING_SUBGRAPH); + expect(get(transactionStore).message).toBe('Waiting for order removal to be indexed...'); + + await vi.advanceTimersByTime(10000); + await indexingPromise; + + expect(get(transactionStore).status).toBe(TransactionStatus.ERROR); + expect(get(transactionStore).message).toBe( + 'The subgraph took too long to respond. Please check again later.' + ); + expect(get(transactionStore).error).toBe(TransactionErrorMessage.TIMEOUT); + + vi.useRealTimers(); + }); }); diff --git a/packages/ui-components/src/lib/components/deployment/DeploymentSteps.svelte b/packages/ui-components/src/lib/components/deployment/DeploymentSteps.svelte index 8d069344d..d9540ad5b 100644 --- a/packages/ui-components/src/lib/components/deployment/DeploymentSteps.svelte +++ b/packages/ui-components/src/lib/components/deployment/DeploymentSteps.svelte @@ -214,7 +214,7 @@ checkingDeployment = true; try { - result = await getDeploymentTransactionArgs(gui, $wagmiConfig, allTokenOutputs); + result = await getDeploymentTransactionArgs(gui, $wagmiConfig); } catch (e) { checkingDeployment = false; error = DeploymentStepErrors.ADD_ORDER_FAILED; diff --git a/packages/ui-components/src/lib/components/deployment/getDeploymentTransactionArgs.ts b/packages/ui-components/src/lib/components/deployment/getDeploymentTransactionArgs.ts index 4070187ce..004198a85 100644 --- a/packages/ui-components/src/lib/components/deployment/getDeploymentTransactionArgs.ts +++ b/packages/ui-components/src/lib/components/deployment/getDeploymentTransactionArgs.ts @@ -4,7 +4,6 @@ import type { DepositAndAddOrderCalldataResult, DotrainOrderGui } from '@rainlanguage/orderbook/js_api'; -import type { OrderIO } from '@rainlanguage/orderbook/js_api'; import type { Hex } from 'viem'; import type { ExtendedApprovalCalldata } from '$lib/stores/transactionStore'; @@ -12,12 +11,7 @@ export enum AddOrderErrors { ADD_ORDER_FAILED = 'Failed to add order', MISSING_GUI = 'Order GUI is required', MISSING_CONFIG = 'Wagmi config is required', - NO_WALLET = 'No wallet address found', - INVALID_CHAIN_ID = 'Invalid chain ID in deployment', - MISSING_ORDERBOOK = 'Orderbook address not found', - TOKEN_INFO_FAILED = 'Failed to fetch token information', - APPROVAL_FAILED = 'Failed to generate approval calldata', - DEPLOYMENT_FAILED = 'Failed to generate deployment calldata' + NO_WALLET = 'No wallet address found' } export interface HandleAddOrderResult { @@ -29,8 +23,7 @@ export interface HandleAddOrderResult { export async function getDeploymentTransactionArgs( gui: DotrainOrderGui | null, - wagmiConfig: Config | undefined, - allTokenOutputs: OrderIO[] + wagmiConfig: Config | undefined ): Promise { if (!gui) { throw new Error(AddOrderErrors.MISSING_GUI); @@ -45,64 +38,8 @@ export async function getDeploymentTransactionArgs( throw new Error(AddOrderErrors.NO_WALLET); } - let approvalResults; - try { - approvalResults = await gui.generateApprovalCalldatas(address); - } catch (error) { - throw new Error( - `${AddOrderErrors.APPROVAL_FAILED}: ${error instanceof Error ? error.message : 'Unknown error'}` - ); - } - - let deploymentCalldata; - try { - deploymentCalldata = await gui.generateDepositAndAddOrderCalldatas(); - } catch (error) { - throw new Error( - `${AddOrderErrors.DEPLOYMENT_FAILED}: ${error instanceof Error ? error.message : 'Unknown error'}` - ); - } - - const currentDeployment = gui.getCurrentDeployment(); - const chainId = currentDeployment?.deployment?.order?.network?.['chain-id'] as number; - if (!chainId) { - throw new Error(AddOrderErrors.INVALID_CHAIN_ID); - } - - // @ts-expect-error orderbook is not typed - const orderbookAddress = currentDeployment?.deployment?.order?.orderbook?.address; - if (!orderbookAddress) { - throw new Error(AddOrderErrors.MISSING_ORDERBOOK); - } - - let approvals: ExtendedApprovalCalldata[] = []; - - try { - const outputTokenInfos = await Promise.all( - allTokenOutputs.map((token) => { - const key = token.token?.key; - if (!key) throw new Error('Token key is missing'); - return gui.getTokenInfo(key); - }) - ); - - if (approvalResults !== 'NoDeposits') { - approvals = approvalResults.Calldatas.map((approval) => { - const token = outputTokenInfos.find((token) => token?.address === approval.token); - if (!token) { - throw new Error(`Token info not found for address: ${approval.token}`); - } - return { - ...approval, - symbol: token.symbol - }; - }); - } - } catch (error) { - throw new Error( - `${AddOrderErrors.TOKEN_INFO_FAILED}: ${error instanceof Error ? error.message : 'Unknown error'}` - ); - } + const { approvals, deploymentCalldata, orderbookAddress, chainId } = + await gui.getDeploymentTransactionArgs(address); return { approvals, diff --git a/packages/ui-components/src/lib/components/detail/OrderDetail.svelte b/packages/ui-components/src/lib/components/detail/OrderDetail.svelte index 3f17a275a..66dadb7d9 100644 --- a/packages/ui-components/src/lib/components/detail/OrderDetail.svelte +++ b/packages/ui-components/src/lib/components/detail/OrderDetail.svelte @@ -94,7 +94,8 @@ order: data.order, onRemove: $orderDetailQuery.refetch, chainId, - orderbookAddress + orderbookAddress, + subgraphUrl } })} disabled={!handleOrderRemoveModal} diff --git a/packages/ui-components/src/lib/stores/transactionStore.ts b/packages/ui-components/src/lib/stores/transactionStore.ts index ffc2bd88d..689b25468 100644 --- a/packages/ui-components/src/lib/stores/transactionStore.ts +++ b/packages/ui-components/src/lib/stores/transactionStore.ts @@ -11,7 +11,11 @@ import type { Vault, WithdrawCalldataResult } from '@rainlanguage/orderbook/js_api'; -import { getTransaction, getTransactionAddOrders } from '@rainlanguage/orderbook/js_api'; +import { + getTransaction, + getTransactionAddOrders, + getTransactionRemoveOrders +} from '@rainlanguage/orderbook/js_api'; export const ADDRESS_ZERO = '0x0000000000000000000000000000000000000000'; export const ONE = BigInt('1000000000000000000'); @@ -68,6 +72,7 @@ export type RemoveOrderTransactionArgs = { orderbookAddress: Hex; removeOrderCalldata: RemoveOrderCalldata; chainId: number; + subgraphUrl: string; }; export type TransactionState = { @@ -166,6 +171,31 @@ const transactionStore = () => { }, 1000); }; + const awaitRemoveOrderIndexing = async (subgraphUrl: string, txHash: string) => { + update((state) => ({ + ...state, + status: TransactionStatus.PENDING_SUBGRAPH, + message: 'Waiting for order removal to be indexed...' + })); + + let attempts = 0; + const interval: NodeJS.Timeout = setInterval(async () => { + attempts++; + const removeOrders = await getTransactionRemoveOrders(subgraphUrl, txHash); + if (attempts >= 10) { + update((state) => ({ + ...state, + message: 'The subgraph took too long to respond. Please check again later.' + })); + clearInterval(interval); + return transactionError(TransactionErrorMessage.TIMEOUT); + } else if (removeOrders?.length > 0) { + clearInterval(interval); + return transactionSuccess(txHash); + } + }, 1000); + }; + const checkingWalletAllowance = (message?: string) => update((state) => ({ ...state, @@ -334,7 +364,8 @@ const transactionStore = () => { config, orderbookAddress, removeOrderCalldata, - chainId + chainId, + subgraphUrl }: RemoveOrderTransactionArgs) => { try { await switchChain(config, { chainId }); @@ -356,7 +387,7 @@ const transactionStore = () => { try { awaitDeployTx(hash); await waitForTransactionReceipt(config, { hash }); - return transactionSuccess(hash, 'Order removed successfully.'); + return awaitRemoveOrderIndexing(subgraphUrl, hash); } catch { return transactionError(TransactionErrorMessage.REMOVE_ORDER_FAILED); } @@ -374,7 +405,8 @@ const transactionStore = () => { transactionSuccess, transactionError, awaitTransactionIndexing, - awaitNewOrderIndexing + awaitNewOrderIndexing, + awaitRemoveOrderIndexing }; }; diff --git a/packages/ui-components/src/lib/types/transaction.ts b/packages/ui-components/src/lib/types/transaction.ts index b24f0f41f..3b33e2c7b 100644 --- a/packages/ui-components/src/lib/types/transaction.ts +++ b/packages/ui-components/src/lib/types/transaction.ts @@ -26,4 +26,5 @@ export type OrderRemoveArgs = { onRemove: () => void; chainId: number; orderbookAddress: Hex; + subgraphUrl: string; }; diff --git a/packages/webapp/src/lib/components/TransactionModal.svelte b/packages/webapp/src/lib/components/TransactionModal.svelte index 182fe024a..78a14b490 100644 --- a/packages/webapp/src/lib/components/TransactionModal.svelte +++ b/packages/webapp/src/lib/components/TransactionModal.svelte @@ -53,12 +53,12 @@