diff --git a/Cargo.lock b/Cargo.lock index 4e4370946..d69c5c1ce 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -6459,13 +6459,17 @@ dependencies = [ "cynic-introspection", "httpmock", "insta", + "js-sys", "rain_orderbook_bindings", "reqwest 0.12.5", "serde", + "serde-wasm-bindgen 0.6.5", "serde_json", "thiserror", "tokio", + "tsify", "typeshare", + "wasm-bindgen", ] [[package]] diff --git a/crates/quote/src/js_api/impls.rs b/crates/quote/src/js_api/impls.rs index 44bf08d28..516075ac7 100644 --- a/crates/quote/src/js_api/impls.rs +++ b/crates/quote/src/js_api/impls.rs @@ -1,5 +1,6 @@ use super::*; use crate::QuoteTarget as MainQuoteTarget; +use crate::{BatchOrderQuotesResponse as MainBatchOrderQuotesResponse, Pair as MainPair}; use crate::{OrderQuoteValue as MainOrderQuoteValue, QuoteSpec as MainQuoteSpec}; use alloy::primitives::{ hex::{encode_prefixed, FromHex}, @@ -106,11 +107,67 @@ impl From for super::QuoteResult { } } +impl From for Pair { + fn from(value: MainPair) -> Self { + Pair { + pair_name: value.pair_name, + input_index: value.input_index, + output_index: value.output_index, + } + } +} +impl From for MainPair { + fn from(value: Pair) -> Self { + MainPair { + pair_name: value.pair_name, + input_index: value.input_index, + output_index: value.output_index, + } + } +} + +impl From for BatchOrderQuotesResponse { + fn from(value: MainBatchOrderQuotesResponse) -> Self { + let mut block_number_error = "block number, ".to_string(); + BatchOrderQuotesResponse { + pair: value.pair.into(), + block_number: u64::try_from(value.block_number) + .inspect_err(|e| block_number_error.push_str(&e.to_string())) + .expect_throw(&block_number_error), + data: value.data.map(OrderQuoteValue::from), + success: value.success, + error: value.error, + } + } +} +impl From for MainBatchOrderQuotesResponse { + fn from(value: BatchOrderQuotesResponse) -> Self { + let mut max_output_error = "max output, ".to_string(); + let mut ratio_error = "ratio, ".to_string(); + MainBatchOrderQuotesResponse { + pair: value.pair.into(), + block_number: U256::from(value.block_number), + data: value.data.map(|e| MainOrderQuoteValue { + max_output: U256::from_str(&e.max_output) + .inspect_err(|e| max_output_error.push_str(&e.to_string())) + .expect_throw(&max_output_error), + ratio: U256::from_str(&e.ratio) + .inspect_err(|e| ratio_error.push_str(&e.to_string())) + .expect_throw(&ratio_error), + }), + success: value.success, + error: value.error, + } + } +} + impl_wasm_traits!(QuoteSpec); impl_wasm_traits!(QuoteTarget); impl_wasm_traits!(QuoteResult); impl_wasm_traits!(BatchQuoteSpec); impl_wasm_traits!(BatchQuoteTarget); +impl_wasm_traits!(Pair); +impl_wasm_traits!(BatchOrderQuotesResponse); #[cfg(test)] mod tests { @@ -179,4 +236,31 @@ mod tests { quote_target.quote_config.order.owner = "0x1234".to_string(); let _ = MainQuoteTarget::from(quote_target); } + + #[wasm_bindgen_test] + fn test_batch_order_quotes_response_roundtrip() { + let main_batch_order_quotes_response = MainBatchOrderQuotesResponse::default(); + let batch_order_quotes_response = + BatchOrderQuotesResponse::from(main_batch_order_quotes_response.clone()); + let expected: MainBatchOrderQuotesResponse = + MainBatchOrderQuotesResponse::from(batch_order_quotes_response.clone()); + assert_eq!(main_batch_order_quotes_response, expected); + + let main_batch_order_quotes_response = + MainBatchOrderQuotesResponse::from(batch_order_quotes_response.clone()); + let expected = BatchOrderQuotesResponse::from(main_batch_order_quotes_response.clone()); + assert_eq!(batch_order_quotes_response, expected); + } + + #[wasm_bindgen_test] + fn test_pair_roundtrip() { + let main_pair = MainPair::default(); + let pair = Pair::from(main_pair.clone()); + let expected = MainPair::from(pair.clone()); + assert_eq!(main_pair, expected); + + let main_pair = MainPair::from(pair.clone()); + let expected = Pair::from(main_pair.clone()); + assert_eq!(pair, expected); + } } diff --git a/crates/quote/src/js_api/mod.rs b/crates/quote/src/js_api/mod.rs index 1803373df..677c4874d 100644 --- a/crates/quote/src/js_api/mod.rs +++ b/crates/quote/src/js_api/mod.rs @@ -1,11 +1,13 @@ use crate::{error::Error, BatchQuoteSpec as MainBatchQuoteSpec, QuoteSpec as MainQuoteSpec}; -use crate::{BatchQuoteTarget as MainBatchQuoteTarget, QuoteTarget as MainQuoteTarget}; +use crate::{ + get_order_quotes, BatchQuoteTarget as MainBatchQuoteTarget, QuoteTarget as MainQuoteTarget, +}; use alloy::primitives::{ hex::{encode_prefixed, FromHex}, Address, U256, }; use rain_orderbook_bindings::js_api::{Quote, SignedContextV1}; -use rain_orderbook_subgraph_client::utils::make_order_id; +use rain_orderbook_subgraph_client::{types::common::Order, utils::make_order_id}; use serde::{Deserialize, Serialize}; use serde_wasm_bindgen::to_value; use std::str::FromStr; @@ -175,3 +177,42 @@ pub async fn get_batch_quote_target_from_subgraph( )?), } } + +#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, Default, Tsify)] +#[serde(rename_all = "camelCase")] +#[tsify(into_wasm_abi, from_wasm_abi)] +pub struct Pair { + pub pair_name: String, + pub input_index: u32, + pub output_index: u32, +} + +#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, Default, Tsify)] +#[serde(rename_all = "camelCase")] +#[tsify(into_wasm_abi, from_wasm_abi)] +pub struct BatchOrderQuotesResponse { + pub pair: Pair, + pub block_number: u64, + pub data: Option, + pub success: bool, + pub error: Option, +} + +/// Get the quote for an order +/// Resolves with a BatchOrderQuotesResponse object +#[wasm_bindgen(js_name = "getOrderQuote")] +pub async fn get_order_quote( + order: Vec, + rpc_url: &str, + block_number: Option, +) -> Result { + Ok(to_value( + &get_order_quotes(order, block_number, rpc_url.to_string()) + .await + .map(|v| { + v.into_iter() + .map(BatchOrderQuotesResponse::from) + .collect::>() + })?, + )?) +} diff --git a/crates/subgraph/Cargo.toml b/crates/subgraph/Cargo.toml index 42fec1848..fa652dc7a 100644 --- a/crates/subgraph/Cargo.toml +++ b/crates/subgraph/Cargo.toml @@ -19,6 +19,10 @@ alloy = { workspace = true, features = ["rand"] } rain_orderbook_bindings = { workspace = true } chrono = { workspace = true } cynic-introspection = "3.7.3" +tsify = { version = "0.4.5", default-features = false, features = ["js", "wasm-bindgen"] } +wasm-bindgen = { version = "0.2.92" } +js-sys = { version = "0.3.69" } +serde-wasm-bindgen = { version = "0.6.5" } [dev-dependencies] insta = { workspace = true } diff --git a/crates/subgraph/src/types/common.rs b/crates/subgraph/src/types/common.rs index bef02ad12..fbf287e21 100644 --- a/crates/subgraph/src/types/common.rs +++ b/crates/subgraph/src/types/common.rs @@ -1,5 +1,6 @@ use crate::schema; use serde::{Deserialize, Serialize}; +use tsify::Tsify; use typeshare::typeshare; #[derive(cynic::QueryVariables, Debug, Clone)] @@ -62,8 +63,9 @@ pub struct PaginationWithTimestampQueryVariables { pub timestamp_lte: Option, } -#[derive(cynic::QueryFragment, Debug, Serialize, Clone)] +#[derive(cynic::QueryFragment, Debug, Serialize, Clone, Tsify)] #[typeshare] +#[tsify(into_wasm_abi, from_wasm_abi)] pub struct Orderbook { pub id: Bytes, } @@ -71,9 +73,11 @@ pub struct Orderbook { #[typeshare] pub type RainMetaV1 = Bytes; -#[derive(cynic::QueryFragment, Debug, Serialize, Clone)] +#[derive(cynic::QueryFragment, Debug, Serialize, Clone, Tsify)] #[typeshare] #[serde(rename_all = "camelCase")] +#[tsify(into_wasm_abi, from_wasm_abi)] +// #[cfg_attr(target_family = "wasm", serde(rename(serialize = "SgOrder")))] pub struct Order { pub id: Bytes, pub order_bytes: Bytes, @@ -83,6 +87,7 @@ pub struct Order { pub inputs: Vec, pub orderbook: Orderbook, pub active: bool, + #[tsify(type = "SgBigInt")] pub timestamp_added: BigInt, pub meta: Option, pub add_events: Vec, @@ -98,10 +103,11 @@ pub struct TradeStructPartialOrder { pub order_hash: Bytes, } -#[derive(cynic::QueryFragment, Debug, Serialize, Clone)] +#[derive(cynic::QueryFragment, Debug, Serialize, Clone, Tsify)] #[cynic(graphql_type = "Order")] #[serde(rename_all = "camelCase")] #[typeshare] +#[tsify(into_wasm_abi, from_wasm_abi)] pub struct OrderAsIO { pub id: Bytes, pub order_hash: Bytes, @@ -134,13 +140,16 @@ pub struct VaultsListQueryVariables { pub filters: Option, } -#[derive(cynic::QueryFragment, Debug, Serialize, Clone)] +#[derive(cynic::QueryFragment, Debug, Serialize, Clone, Tsify)] #[typeshare] #[serde(rename_all = "camelCase")] +#[tsify(into_wasm_abi, from_wasm_abi)] pub struct Vault { pub id: Bytes, pub owner: Bytes, + #[tsify(type = "SgBigInt")] pub vault_id: BigInt, + #[tsify(type = "SgBigInt")] pub balance: BigInt, pub token: Erc20, pub orderbook: Orderbook, @@ -153,11 +162,13 @@ pub struct Vault { pub balance_changes: Vec, } -#[derive(cynic::QueryFragment, Debug, Clone, Serialize)] +#[derive(cynic::QueryFragment, Debug, Clone, Serialize, Tsify)] #[cynic(graphql_type = "Vault")] #[typeshare] +#[tsify(into_wasm_abi, from_wasm_abi)] pub struct VaultBalanceChangeVault { pub id: Bytes, + #[tsify(type = "SgBigInt")] pub vault_id: BigInt, pub token: Erc20, } @@ -177,10 +188,11 @@ pub struct VaultBalanceChangeUnwrapped { pub orderbook: Orderbook, } -#[derive(cynic::InlineFragments, Debug, Clone, Serialize)] +#[derive(cynic::InlineFragments, Debug, Clone, Serialize, Tsify)] #[serde(tag = "__typename", content = "data")] #[serde(rename_all = "camelCase")] #[typeshare] +#[tsify(into_wasm_abi, from_wasm_abi)] pub enum VaultBalanceChange { Withdrawal(Withdrawal), TradeVaultBalanceChange(TradeVaultBalanceChange), @@ -190,61 +202,81 @@ pub enum VaultBalanceChange { Unknown, } -#[derive(cynic::QueryFragment, Debug, Clone, Serialize)] +#[derive(cynic::QueryFragment, Debug, Clone, Serialize, Tsify)] #[typeshare] #[serde(rename_all = "camelCase")] +#[tsify(into_wasm_abi, from_wasm_abi)] pub struct Deposit { pub id: Bytes, pub __typename: String, + #[tsify(type = "SgBigInt")] pub amount: BigInt, + #[tsify(type = "SgBigInt")] pub new_vault_balance: BigInt, + #[tsify(type = "SgBigInt")] pub old_vault_balance: BigInt, pub vault: VaultBalanceChangeVault, + #[tsify(type = "SgBigInt")] pub timestamp: BigInt, pub transaction: Transaction, pub orderbook: Orderbook, } -#[derive(cynic::QueryFragment, Debug, Clone, Serialize)] +#[derive(cynic::QueryFragment, Debug, Clone, Serialize, Tsify)] #[typeshare] #[serde(rename_all = "camelCase")] +#[tsify(into_wasm_abi, from_wasm_abi)] pub struct Withdrawal { pub id: Bytes, pub __typename: String, + #[tsify(type = "SgBigInt")] pub amount: BigInt, + #[tsify(type = "SgBigInt")] pub new_vault_balance: BigInt, + #[tsify(type = "SgBigInt")] pub old_vault_balance: BigInt, pub vault: VaultBalanceChangeVault, + #[tsify(type = "SgBigInt")] pub timestamp: BigInt, pub transaction: Transaction, pub orderbook: Orderbook, } -#[derive(cynic::QueryFragment, Debug, Clone, Serialize)] +#[derive(cynic::QueryFragment, Debug, Clone, Serialize, Tsify)] #[typeshare] #[serde(rename_all = "camelCase")] +#[tsify(into_wasm_abi, from_wasm_abi)] pub struct TradeVaultBalanceChange { pub id: Bytes, pub __typename: String, + #[tsify(type = "SgBigInt")] pub amount: BigInt, + #[tsify(type = "SgBigInt")] pub new_vault_balance: BigInt, + #[tsify(type = "SgBigInt")] pub old_vault_balance: BigInt, pub vault: VaultBalanceChangeVault, + #[tsify(type = "SgBigInt")] pub timestamp: BigInt, pub transaction: Transaction, pub orderbook: Orderbook, } -#[derive(cynic::QueryFragment, Debug, Clone, Serialize)] +#[derive(cynic::QueryFragment, Debug, Clone, Serialize, Tsify)] #[typeshare] #[serde(rename_all = "camelCase")] +#[tsify(into_wasm_abi, from_wasm_abi)] pub struct ClearBounty { pub id: Bytes, pub __typename: String, + #[tsify(type = "SgBigInt")] pub amount: BigInt, + #[tsify(type = "SgBigInt")] pub new_vault_balance: BigInt, + #[tsify(type = "SgBigInt")] pub old_vault_balance: BigInt, pub vault: VaultBalanceChangeVault, + #[tsify(type = "SgBigInt")] pub timestamp: BigInt, pub transaction: Transaction, pub orderbook: Orderbook, @@ -271,46 +303,56 @@ pub struct Trade { pub orderbook: Orderbook, } -#[derive(cynic::QueryFragment, Debug, Clone, Serialize)] +#[derive(cynic::QueryFragment, Debug, Clone, Serialize, Tsify)] #[cynic(graphql_type = "Trade")] #[typeshare] +#[tsify(into_wasm_abi, from_wasm_abi)] pub struct OrderStructPartialTrade { pub id: Bytes, } -#[derive(cynic::QueryFragment, Debug, Serialize, Clone, PartialEq)] +#[derive(cynic::QueryFragment, Debug, Serialize, Clone, PartialEq, Tsify)] #[cynic(graphql_type = "ERC20")] #[typeshare] +#[tsify(into_wasm_abi, from_wasm_abi)] pub struct Erc20 { pub id: Bytes, pub address: Bytes, pub name: Option, pub symbol: Option, + #[tsify(type = "SgBigInt")] pub decimals: Option, } -#[derive(cynic::QueryFragment, Debug, Serialize, Clone)] +#[derive(cynic::QueryFragment, Debug, Serialize, Clone, Tsify)] #[typeshare] #[serde(rename_all = "camelCase")] +#[tsify(into_wasm_abi, from_wasm_abi)] pub struct Transaction { pub id: Bytes, pub from: Bytes, + #[tsify(type = "SgBigInt")] pub block_number: BigInt, + #[tsify(type = "SgBigInt")] pub timestamp: BigInt, } -#[derive(cynic::QueryFragment, Debug, Serialize, Clone)] +#[derive(cynic::QueryFragment, Debug, Serialize, Clone, Tsify)] #[typeshare] +#[tsify(into_wasm_abi, from_wasm_abi)] pub struct AddOrder { pub transaction: Transaction, } -#[derive(cynic::Scalar, Debug, Clone, PartialEq)] +#[derive(cynic::Scalar, Debug, Clone, PartialEq, Tsify)] #[typeshare] +#[tsify(into_wasm_abi, from_wasm_abi)] +#[serde(rename = "SgBigInt")] pub struct BigInt(pub String); -#[derive(cynic::Scalar, Debug, Clone, PartialEq)] +#[derive(cynic::Scalar, Debug, Clone, PartialEq, Tsify)] #[typeshare] +#[tsify(into_wasm_abi, from_wasm_abi)] pub struct Bytes(pub String); #[derive(cynic::Enum, Clone, Copy, Debug)] diff --git a/crates/subgraph/src/types/impls.rs b/crates/subgraph/src/types/impls.rs new file mode 100644 index 000000000..d801d4619 --- /dev/null +++ b/crates/subgraph/src/types/impls.rs @@ -0,0 +1,31 @@ +#[cfg(target_family = "wasm")] +mod js_api { + use super::super::common::{ + AddOrder, BigInt, Bytes, ClearBounty, Deposit, Erc20, Order, OrderAsIO, + OrderStructPartialTrade, TradeVaultBalanceChange, Transaction, Vault, VaultBalanceChange, + VaultBalanceChangeVault, Withdrawal, + }; + use rain_orderbook_bindings::impl_wasm_traits; + use serde_wasm_bindgen::{from_value, to_value}; + use wasm_bindgen::{convert::*, describe::WasmDescribe}; + use wasm_bindgen::{ + describe::{inform, WasmDescribeVector, VECTOR}, + JsValue, UnwrapThrowExt, + }; + + impl_wasm_traits!(Order); + impl_wasm_traits!(Vault); + impl_wasm_traits!(AddOrder); + impl_wasm_traits!(OrderAsIO); + impl_wasm_traits!(VaultBalanceChangeVault); + impl_wasm_traits!(VaultBalanceChange); + impl_wasm_traits!(Withdrawal); + impl_wasm_traits!(TradeVaultBalanceChange); + impl_wasm_traits!(Deposit); + impl_wasm_traits!(ClearBounty); + impl_wasm_traits!(OrderStructPartialTrade); + impl_wasm_traits!(Erc20); + impl_wasm_traits!(Transaction); + impl_wasm_traits!(BigInt); + impl_wasm_traits!(Bytes); +} diff --git a/crates/subgraph/src/types/mod.rs b/crates/subgraph/src/types/mod.rs index 88b092954..b653da9b9 100644 --- a/crates/subgraph/src/types/mod.rs +++ b/crates/subgraph/src/types/mod.rs @@ -1,4 +1,5 @@ pub mod common; +mod impls; pub mod order; pub mod order_detail_traits; pub mod order_trade; diff --git a/tauri-app/src-tauri/Cargo.lock b/tauri-app/src-tauri/Cargo.lock index 7f0b16430..49665c07d 100644 --- a/tauri-app/src-tauri/Cargo.lock +++ b/tauri-app/src-tauri/Cargo.lock @@ -8371,12 +8371,16 @@ dependencies = [ "cynic", "cynic-codegen", "cynic-introspection", + "js-sys", "rain_orderbook_bindings", "reqwest 0.12.5", "serde", + "serde-wasm-bindgen 0.6.5", "serde_json", "thiserror", + "tsify", "typeshare 1.0.1", + "wasm-bindgen", ] [[package]] diff --git a/test-js/quote/test.test.ts b/test-js/quote/test.test.ts index a1bcbc087..515788982 100644 --- a/test-js/quote/test.test.ts +++ b/test-js/quote/test.test.ts @@ -5,11 +5,14 @@ import { QuoteSpec, QuoteTarget, OrderQuoteValue, + BatchOrderQuotesResponse, + Order, } from "../../dist/types/quote"; import { getId, doQuoteTargets, getQuoteTargetFromSubgraph, + getOrderQuote, } from "../../dist/cjs/quote"; describe("Rain Orderbook Quote Package Bindgen Tests", async function () { @@ -216,4 +219,96 @@ describe("Rain Orderbook Quote Package Bindgen Tests", async function () { assert.fail("expected to resolve, but failed"); } }); + it("should get order quote", async () => { + await mockServer.forPost("/rpc-url").once().thenSendJsonRpcResult("0x01"); + await mockServer + .forPost("/rpc-url") + .thenSendJsonRpcResult( + "0x000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000060000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000002" + ); + const order: Order = { + id: "0x46891c626a8a188610b902ee4a0ce8a7e81915e1b922584f8168d14525899dfb", + orderBytes: + "0x0000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000a0000000000000000000000000000000000000000000000000000000000000012000000000000000000000000000000000000000000000000000000000000001a00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000060000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000010000000000000000000000001111111111111111111111111111111111111111000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001000000000000000000000000222222222222222222222222222222222222222200000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + orderHash: + "0x283508c8f56f4de2f21ee91749d64ec3948c16bc6b4bfe4f8d11e4e67d76f4e0", + owner: "0x0000000000000000000000000000000000000000", + outputs: [ + { + id: "0x0000000000000000000000000000000000000000", + token: { + id: "0x0000000000000000000000000000000000000000", + address: "0x2222222222222222222222222222222222222222", + name: "T1", + symbol: "T1", + decimals: "0", + }, + balance: "0", + vaultId: "0", + owner: "0x0000000000000000000000000000000000000000", + ordersAsOutput: [], + ordersAsInput: [], + balanceChanges: [], + orderbook: { + id: "0x0000000000000000000000000000000000000000", + }, + }, + ], + inputs: [ + { + id: "0x0000000000000000000000000000000000000000", + token: { + id: "0x0000000000000000000000000000000000000000", + address: "0x1111111111111111111111111111111111111111", + name: "T2", + symbol: "T2", + decimals: "0", + }, + balance: "0", + vaultId: "0", + owner: "0x0000000000000000000000000000000000000000", + ordersAsOutput: [], + ordersAsInput: [], + balanceChanges: [], + orderbook: { + id: "0x0000000000000000000000000000000000000000", + }, + }, + ], + active: true, + addEvents: [ + { + transaction: { + blockNumber: "0", + timestamp: "0", + id: "0x0000000000000000000000000000000000000000", + from: "0x0000000000000000000000000000000000000000", + }, + }, + ], + meta: null, + timestampAdded: "0", + orderbook: { + id: "0x0000000000000000000000000000000000000000", + }, + trades: [], + }; + const result = await getOrderQuote([order], mockServer.url + "/rpc-url"); + const expected: BatchOrderQuotesResponse[] = [ + { + pair: { pairName: "T2/T1", inputIndex: 0, outputIndex: 0 }, + blockNumber: 1, + data: { + maxOutput: + "0x0000000000000000000000000000000000000000000000000000000000000001", + ratio: + "0x0000000000000000000000000000000000000000000000000000000000000002", + }, + success: true, + error: undefined, + }, + ]; + + assert.deepEqual(result, expected); + }); });