Skip to content

Commit

Permalink
Merge pull request #1255 from rainlanguage/2025-01-10-remove-order-bu…
Browse files Browse the repository at this point in the history
…tton

Adding remove order button to order details page on webapp
  • Loading branch information
hardyjosh authored Feb 10, 2025
2 parents 79d8f55 + b246e6e commit 04abaeb
Show file tree
Hide file tree
Showing 8 changed files with 213 additions and 11 deletions.
20 changes: 19 additions & 1 deletion crates/common/src/js_api/mod.rs
Original file line number Diff line number Diff line change
@@ -1,13 +1,21 @@
use crate::{
add_order::{AddOrderArgs, AddOrderArgsError},
frontmatter::parse_frontmatter,
remove_order::{RemoveOrderArgs, RemoveOrderArgsError},
transaction::TransactionArgs,
};
use alloy::primitives::Bytes;
use js_sys::Uint8Array;
use rain_orderbook_app_settings::{Config, ParseConfigSourceError};
use rain_orderbook_bindings::{impl_all_wasm_traits, wasm_traits::prelude::*};
use rain_orderbook_subgraph_client::types::common::Order;
use serde::{Deserialize, Serialize};
use std::ops::Deref;
use thiserror::Error;
use wasm_bindgen::prelude::*;

#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Tsify)]
pub struct RemoveOrderCalldata(Bytes);
impl_all_wasm_traits!(RemoveOrderCalldata);

/// Represents all possible errors of this module
#[derive(Debug, Error)]
Expand All @@ -18,6 +26,8 @@ pub enum Error {
ParseConfigSourceError(#[from] ParseConfigSourceError),
#[error(transparent)]
AddOrderArgsError(#[from] AddOrderArgsError),
#[error(transparent)]
RemoveOrderArgsError(#[from] RemoveOrderArgsError),
}

impl From<Error> for JsValue {
Expand Down Expand Up @@ -48,3 +58,11 @@ pub async fn get_add_order_calldata(dotrain: &str, deployment: &str) -> Result<U
.as_slice()
.into())
}

/// Get removeOrder() calldata for a given order
#[wasm_bindgen(js_name = "getRemoveOrderCalldata")]
pub async fn get_remove_order_calldata(order: Order) -> Result<RemoveOrderCalldata, Error> {
let remove_order_args = RemoveOrderArgs { order };
let calldata = remove_order_args.get_rm_order_calldata().await?;
Ok(RemoveOrderCalldata(Bytes::copy_from_slice(&calldata)))
}
Original file line number Diff line number Diff line change
Expand Up @@ -16,14 +16,12 @@
import { createQuery } from '@tanstack/svelte-query';
import { Button, TabItem, Tabs } from 'flowbite-svelte';
import { onDestroy } from 'svelte';
import type { Readable, Writable } from 'svelte/store';
import type { Writable } from 'svelte/store';
import OrderApy from '../tables/OrderAPY.svelte';
import { page } from '$app/stores';
import DepositOrWithdrawButtons from './DepositOrWithdrawButtons.svelte';
import type { Config } from 'wagmi';
export let walletAddressMatchesOrBlank: Readable<(address: string) => boolean> | undefined =
undefined;
export let handleDepositOrWithdrawModal:
| ((args: {
vault: Vault;
Expand All @@ -34,7 +32,13 @@
}) => void)
| undefined = undefined;
export let handleOrderRemoveModal:
| ((order: OrderSubgraph, refetch: () => void) => void)
| ((args: {
order: OrderSubgraph;
onRemove: () => void;
wagmiConfig: Config;
chainId: number;
orderbookAddress: string;
}) => void)
| undefined = undefined;
export let handleQuoteDebugModal:
| undefined
Expand Down Expand Up @@ -96,11 +100,18 @@
</div>
<BadgeActive active={data.active} large />
</div>
{#if data && $walletAddressMatchesOrBlank?.(data.owner) && data.active && handleOrderRemoveModal}
{#if data && $signerAddress === data.owner && data.active && handleOrderRemoveModal && $wagmiConfig && chainId && orderbookAddress}
<Button
data-testid="remove-button"
color="dark"
on:click={() => handleOrderRemoveModal(data, $orderDetailQuery.refetch)}
on:click={() =>
handleOrderRemoveModal({
order: data,
onRemove: $orderDetailQuery.refetch,
wagmiConfig: $wagmiConfig,
chainId,
orderbookAddress
})}
disabled={!handleOrderRemoveModal}
>
Remove
Expand Down
46 changes: 45 additions & 1 deletion packages/ui-components/src/lib/stores/transactionStore.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import type {
ApprovalCalldata,
DepositAndAddOrderCalldataResult,
DepositCalldataResult,
RemoveOrderCalldata,
Vault,
WithdrawCalldataResult
} from '@rainlanguage/orderbook/js_api';
Expand Down Expand Up @@ -33,7 +34,8 @@ export enum TransactionErrorMessage {
DEPLOYMENT_FAILED = 'Deployment transaction failed.',
SWITCH_CHAIN_FAILED = 'Failed to switch chain.',
DEPOSIT_FAILED = 'Failed to deposit tokens.',
WITHDRAWAL_FAILED = 'Failed to withdraw tokens.'
WITHDRAWAL_FAILED = 'Failed to withdraw tokens.',
REMOVE_ORDER_FAILED = 'Failed to remove order.'
}

type ExtendedApprovalCalldata = ApprovalCalldata & { symbol?: string };
Expand All @@ -55,6 +57,13 @@ export type DepositOrWithdrawTransactionArgs = {
vault: Vault;
};

export type RemoveOrderTransactionArgs = {
config: Config;
orderbookAddress: Hex;
removeOrderCalldata: RemoveOrderCalldata;
chainId: number;
};

export type TransactionState = {
status: TransactionStatus;
error: string;
Expand All @@ -68,6 +77,8 @@ export type TransactionStore = {
subscribe: (run: (value: TransactionState) => void) => () => void;
reset: () => void;
handleDeploymentTransaction: (args: DeploymentTransactionArgs) => Promise<void>;
handleDepositOrWithdrawTransaction: (args: DepositOrWithdrawTransactionArgs) => Promise<void>;
handleRemoveOrderTransaction: (args: RemoveOrderTransactionArgs) => Promise<void>;
checkingWalletAllowance: (message?: string) => void;
awaitWalletConfirmation: (message?: string) => void;
awaitApprovalTx: (hash: string) => void;
Expand Down Expand Up @@ -240,11 +251,44 @@ const transactionStore = () => {
}
};

const handleRemoveOrderTransaction = async ({
config,
orderbookAddress,
removeOrderCalldata,
chainId
}: RemoveOrderTransactionArgs) => {
try {
await switchChain(config, { chainId });
} catch {
return transactionError(TransactionErrorMessage.SWITCH_CHAIN_FAILED);
}

let hash: Hex;
try {
awaitWalletConfirmation('Please confirm order removal in your wallet...');
hash = await sendTransaction(config, {
to: orderbookAddress,
data: removeOrderCalldata as `0x${string}`
});
} catch {
return transactionError(TransactionErrorMessage.USER_REJECTED_TRANSACTION);
}

try {
awaitDeployTx(hash);
await waitForTransactionReceipt(config, { hash });
return transactionSuccess(hash, 'Order removed successfully.');
} catch {
return transactionError(TransactionErrorMessage.REMOVE_ORDER_FAILED);
}
};

return {
subscribe,
reset,
handleDeploymentTransaction,
handleDepositOrWithdrawTransaction,
handleRemoveOrderTransaction,
checkingWalletAllowance,
awaitWalletConfirmation,
awaitApprovalTx,
Expand Down
69 changes: 69 additions & 0 deletions packages/webapp/src/__tests__/OrderRemoveModal.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
import { describe, it, expect, vi, beforeEach } from 'vitest';
import { render, screen, fireEvent } from '@testing-library/svelte';
import OrderRemoveModal from '$lib/components/OrderRemoveModal.svelte';
import { transactionStore } from '@rainlanguage/ui-components';
import type { ComponentProps } from 'svelte';

export type ModalProps = ComponentProps<OrderRemoveModal>;

vi.mock('@rainlanguage/orderbook/js_api', () => ({
getRemoveOrderCalldata: vi.fn().mockResolvedValue('0x123')
}));

vi.useFakeTimers();

describe('OrderRemoveModal', () => {
const mockOrder = {
id: '1',
orderHash: '0x123',
owner: '0x456'
};

const defaultProps = {
open: true,
order: mockOrder,
onRemove: vi.fn(),
wagmiConfig: {},
chainId: 1,
orderbookAddress: '0x789'
} as unknown as ModalProps;

beforeEach(() => {
vi.clearAllMocks();
transactionStore.reset();
});

it('handles transaction correctly', async () => {
const handleTransactionSpy = vi.spyOn(transactionStore, 'handleRemoveOrderTransaction');
render(OrderRemoveModal, defaultProps);

await vi.runAllTimersAsync();

expect(handleTransactionSpy).toHaveBeenCalledWith(
expect.objectContaining({
chainId: 1,
orderbookAddress: '0x789',
config: {}
})
);
});

it('closes modal and resets transaction store', async () => {
render(OrderRemoveModal, defaultProps);
const resetSpy = vi.spyOn(transactionStore, 'reset');

const closeButton = screen.getByLabelText('Close modal');
await fireEvent.click(closeButton);

expect(resetSpy).toHaveBeenCalled();
});

it('calls onRemove callback after successful transaction', async () => {
render(OrderRemoveModal, defaultProps);

transactionStore.transactionSuccess('0x123');
await vi.runAllTimersAsync();

expect(defaultProps.onRemove).toHaveBeenCalled();
});
});
45 changes: 45 additions & 0 deletions packages/webapp/src/lib/components/OrderRemoveModal.svelte
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
<script lang="ts">
import { transactionStore } from '@rainlanguage/ui-components';
import TransactionModal from './TransactionModal.svelte';
import type { OrderSubgraph } from '@rainlanguage/orderbook/js_api';
import { getRemoveOrderCalldata } from '@rainlanguage/orderbook/js_api';
import type { Config } from 'wagmi';
export let open: boolean;
export let order: OrderSubgraph;
export let onRemove: () => void;
export let wagmiConfig: Config;
export let chainId: number;
export let orderbookAddress: string;
const messages = {
success: 'Order was successfully removed.',
pending: 'Removing order...',
error: 'Could not remove order.'
};
function handleSuccess() {
setTimeout(() => {
onRemove();
}, 5000);
}
function handleClose() {
transactionStore.reset();
open = false;
}
async function handleTransaction() {
const removeOrderCalldata = await getRemoveOrderCalldata(order);
transactionStore.handleRemoveOrderTransaction({
config: wagmiConfig,
removeOrderCalldata,
orderbookAddress: orderbookAddress as `0x${string}`,
chainId
});
}
handleTransaction();
</script>

<TransactionModal bind:open {messages} on:close={handleClose} on:success={handleSuccess} />
16 changes: 16 additions & 0 deletions packages/webapp/src/lib/services/modal.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,14 @@
import DeployModal from '$lib/components/DeployModal.svelte';
import DepositOrWithdrawModal from '$lib/components/DepositOrWithdrawModal.svelte';
import OrderRemoveModal from '$lib/components/OrderRemoveModal.svelte';
import type {
ApprovalCalldataResult,
DepositAndAddOrderCalldataResult,
OrderSubgraph,
Vault
} from '@rainlanguage/orderbook/js_api';
import type { Hex } from 'viem';
import type { Config } from 'wagmi';

export type DeployModalProps = {
approvals: ApprovalCalldataResult;
Expand All @@ -22,10 +25,23 @@ export type DepositOrWithdrawModalProps = {
rpcUrl: string;
};

export type OrderRemoveModalProps = {
order: OrderSubgraph;
onRemove: () => void;
open?: boolean;
wagmiConfig: Config;
chainId: number;
orderbookAddress: string;
};

export const handleDeployModal = (args: DeployModalProps) => {
new DeployModal({ target: document.body, props: { open: true, ...args } });
};

export const handleDepositOrWithdrawModal = (args: DepositOrWithdrawModalProps) => {
new DepositOrWithdrawModal({ target: document.body, props: { open: true, ...args } });
};

export const handleOrderRemoveModal = (args: OrderRemoveModalProps) => {
new OrderRemoveModal({ target: document.body, props: { open: true, ...args } });
};
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
import { page } from '$app/stores';
import { codeMirrorTheme, lightweightChartsTheme, colorTheme } from '$lib/darkMode';
import { handleDepositOrWithdrawModal } from '$lib/services/modal';
import { handleDepositOrWithdrawModal, handleOrderRemoveModal } from '$lib/services/modal';
import { wagmiConfig, signerAddress } from '$lib/stores/wagmi';
const { id, network } = $page.params;
const { settings } = $page.data.stores;
Expand All @@ -25,5 +25,6 @@
{chainId}
{wagmiConfig}
{handleDepositOrWithdrawModal}
{handleOrderRemoveModal}
{signerAddress}
/>
2 changes: 0 additions & 2 deletions tauri-app/src/routes/orders/[network]-[id]/+page.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@
import { codeMirrorTheme, lightweightChartsTheme, colorTheme } from '$lib/stores/darkMode';
import { settings } from '$lib/stores/settings';
import { handleDebugTradeModal, handleQuoteDebugModal } from '$lib/services/modal';
import { walletAddressMatchesOrBlank } from '$lib/stores/wallets';
const { id, network } = $page.params;
Expand All @@ -27,7 +26,6 @@
{handleQuoteDebugModal}
{handleDebugTradeModal}
{orderbookAddress}
{walletAddressMatchesOrBlank}
{chainId}
/>
{/if}

0 comments on commit 04abaeb

Please sign in to comment.