Skip to content

Commit

Permalink
feat!: cross-chain tx and skip go integration (#194)
Browse files Browse the repository at this point in the history
  • Loading branch information
chalabi2 authored Feb 6, 2025
1 parent 08d861f commit 64be274
Show file tree
Hide file tree
Showing 41 changed files with 2,057 additions and 414 deletions.
32 changes: 26 additions & 6 deletions .env.test
Original file line number Diff line number Diff line change
@@ -1,10 +1,30 @@
# Wallet
NEXT_PUBLIC_WALLETCONNECT_KEY=test_walletconnect_key
NEXT_PUBLIC_WEB3AUTH_NETWORK=testnet
NEXT_PUBLIC_WEB3AUTH_CLIENT_ID=test_client_id
NEXT_PUBLIC_CHAIN=manifest
NEXT_PUBLIC_CHAIN_ID=test_chain_id

# Chains
NEXT_PUBLIC_CHAIN=manifesttestnet
NEXT_PUBLIC_OSMOSIS_CHAIN=osmosistestnet
NEXT_PUBLIC_CHAIN_ID='manifest-ledger-testnet'
NEXT_PUBLIC_OSMOSIS_CHAIN_ID='osmo-test-5'
NEXT_PUBLIC_AXELAR_CHAIN=axelartestnet
NEXT_PUBLIC_AXELAR_CHAIN_ID='axelar-testnet-lisbon-3'

# Ops
NEXT_PUBLIC_CHAIN_TIER=testnet
NEXT_PUBLIC_RPC_URL=https://test.rpc.url
NEXT_PUBLIC_API_URL=https://test.api.url
NEXT_PUBLIC_EXPLORER_URL=https://test.explorer.url
NEXT_PUBLIC_INDEXER_URL=https://test.indexer.url
# Explorer URLs
NEXT_PUBLIC_EXPLORER_URL=https://testnet.manifest.explorers.guru
NEXT_PUBLIC_OSMOSIS_EXPLORER_URL=https://manifest.io/testnet-osmosis
NEXT_PUBLIC_AXELAR_EXPLORER_URL=https://manifest.io/testnet-axelar
# Indexer URL
NEXT_PUBLIC_INDEXER_URL=
# RPC URLs
NEXT_PUBLIC_RPC_URL=
NEXT_PUBLIC_API_URL=
# Osmosis RPC URLs
NEXT_PUBLIC_OSMOSIS_API_URL=
NEXT_PUBLIC_OSMOSIS_RPC_URL=
# Axelar RPC URLs
NEXT_PUBLIC_AXELAR_API_URL=
NEXT_PUBLIC_AXELAR_RPC_URL=
4 changes: 3 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -38,4 +38,6 @@ next-env.d.ts

.idea/

certificates
certificates

/scripts/*.json
8 changes: 8 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,10 @@ NEXT_PUBLIC_RPC_URL=
NEXT_PUBLIC_API_URL=
NEXT_PUBLIC_EXPLORER_URL=
NEXT_PUBLIC_INDEXER_URL=
NEXT_PUBLIC_OSMOSIS_CHAIN_ID=
NEXT_PUBLIC_OSMOSIS_RPC_URL=
NEXT_PUBLIC_OSMOSIS_API_URL=
NEXT_PUBLIC_OSMOSIS_EXPLORER_URL=
```

where
Expand All @@ -45,6 +49,10 @@ where
- `NEXT_PUBLIC_API_URL` is the chain API URL
- `NEXT_PUBLIC_EXPLORER_URL` is the block explorer URL
- `NEXT_PUBLIC_INDEXER_URL` is the YACI indexer URL
- `NEXT_PUBLIC_OSMOSIS_CHAIN_ID` is the osmosis chain ID
- `NEXT_PUBLIC_OSMOSIS_RPC_URL` is the osmosis RPC URL
- `NEXT_PUBLIC_OSMOSIS_API_URL` is the osmosis API URL
- `NEXT_PUBLIC_OSMOSIS_EXPLORER_URL` is the osmosis block explorer URL

### Development

Expand Down
Binary file added bun.lockb
Binary file not shown.
59 changes: 29 additions & 30 deletions components/bank/components/__tests__/sendBox.test.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { test, expect, afterEach, describe, mock, jest } from 'bun:test';
import React from 'react';
import matchers from '@testing-library/jest-dom/matchers';
import { screen, cleanup, waitFor, fireEvent } from '@testing-library/react';
import { screen, cleanup, waitFor, fireEvent, within } from '@testing-library/react';
import SendBox from '@/components/bank/components/sendBox';
import { mockBalances } from '@/tests/mock';
import { renderWithChainProvider } from '@/tests/render';
Expand All @@ -17,19 +17,33 @@ mock.module('next/router', () => ({
}),
}));

// Add this mock before your tests
mock.module('next/image', () => ({
default: (props: any) => {
// eslint-disable-next-line @next/next/no-img-element
return <img {...props} alt={props.alt || ''} />;
},
}));

const renderWithProps = (props = {}) => {
const defaultProps = {
address: 'test_address',
balances: mockBalances,
isBalancesLoading: false,
refetchBalances: () => {},
refetchHistory: () => {},
osmosisBalances: [],
isOsmosisBalancesLoading: false,
refetchOsmosisBalances: () => {},
resolveOsmosisRefetch: () => {},
};
return renderWithChainProvider(<SendBox {...defaultProps} {...props} />);
};

describe('SendBox', () => {
afterEach(() => {
cleanup();
mock.restore();
});

test('renders correctly', () => {
Expand All @@ -40,42 +54,27 @@ describe('SendBox', () => {

test('toggles between Send and Cross-Chain Transfer', async () => {
renderWithProps();
expect(screen.getByText('Amount')).toBeInTheDocument();

fireEvent.click(screen.getByText('Cross-Chain Transfer'));
await waitFor(() => expect(screen.getByText('Chain')).toBeInTheDocument());
});

test('displays chain selection dropdown when in Cross-Chain Transfer mode', async () => {
renderWithProps();
fireEvent.click(screen.getByText('Cross-Chain Transfer'));
await waitFor(() => expect(screen.getByText('Chain')).toBeInTheDocument());
});

test('selects a chain in Cross-Chain Transfer mode', async () => {
renderWithProps();
const crossChainBtn = screen.getByLabelText('cross-chain-transfer-tab');
fireEvent.click(crossChainBtn);
// Check initial send form
expect(screen.getByPlaceholderText('0.00')).toBeInTheDocument();
expect(screen.queryByLabelText('to-chain-selector')).not.toBeInTheDocument();

await waitFor(() => {
const chainSelector = screen.getByLabelText('chain-selector');
expect(chainSelector).toBeTruthy();
});

const chainSelector = screen.getByLabelText('chain-selector');
fireEvent.click(chainSelector);
// Switch to cross-chain transfer
fireEvent.click(screen.getByLabelText('cross-chain-transfer-tab'));

// Verify cross-chain elements are present
await waitFor(() => {
const osmosisOption = screen.getByText('Osmosis');
expect(osmosisOption).toBeTruthy();
expect(screen.getByLabelText('from-chain-selector')).toBeInTheDocument();
expect(screen.getByLabelText('to-chain-selector')).toBeInTheDocument();
});
});

const osmosisOption = screen.getByText('Osmosis');
fireEvent.click(osmosisOption);
test('displays chain selection dropdowns in Cross-Chain Transfer mode', async () => {
renderWithProps();
fireEvent.click(screen.getByLabelText('cross-chain-transfer-tab'));

await waitFor(() => {
const updatedChainSelector = screen.getByLabelText('chain-selector');
expect(updatedChainSelector.textContent).toContain('Osmosis');
expect(screen.getByLabelText('from-chain-selector')).toBeInTheDocument();
expect(screen.getByLabelText('to-chain-selector')).toBeInTheDocument();
});
});
});
1 change: 1 addition & 0 deletions components/bank/components/__tests__/tokenList.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@ expect.extend(matchers);
describe('TokenList', () => {
afterEach(() => {
cleanup();
mock.restore();
});

test('renders correctly', () => {
Expand Down
109 changes: 83 additions & 26 deletions components/bank/components/sendBox.tsx
Original file line number Diff line number Diff line change
@@ -1,14 +1,16 @@
import { useState } from 'react';
import { useEffect, useMemo, useState } from 'react';
import SendForm from '../forms/sendForm';
import IbcSendForm from '../forms/ibcSendForm';

import env from '@/config/env';
import { CombinedBalanceInfo } from '@/utils/types';
import { ChainContext } from '@cosmos-kit/core';

export interface IbcChain {
id: string;
name: string;
icon: string;
prefix: string;
chainID: string;
}

export default function SendBox({
Expand All @@ -21,6 +23,11 @@ export default function SendBox({
isGroup,
admin,
refetchProposals,
osmosisBalances,
isOsmosisBalancesLoading,
refetchOsmosisBalances,
resolveOsmosisRefetch,
chains,
}: {
address: string;
balances: CombinedBalanceInfo[];
Expand All @@ -31,17 +38,55 @@ export default function SendBox({
selectedDenom?: string;
isGroup?: boolean;
admin?: string;
osmosisBalances: CombinedBalanceInfo[];
isOsmosisBalancesLoading: boolean;
refetchOsmosisBalances: () => void;
resolveOsmosisRefetch: () => void;
chains: Record<string, ChainContext>;
}) {
const ibcChains = useMemo<IbcChain[]>(
() => [
{
id: env.chain,
name: 'Manifest',
icon: 'logo.svg',
prefix: 'manifest',
chainID: env.chainId,
},
{
id: env.osmosisChain,
name: 'Osmosis',
icon: 'osmosis.svg',
prefix: 'osmo',
chainID: env.osmosisChainId,
},
{
id: env.axelarChain,
name: 'Axelar',
icon: 'https://github.com/cosmos/chain-registry/raw/refs/heads/master/axelar/images/axl.svg',
prefix: 'axelar',
chainID: env.axelarChainId,
},
],
[]
);
const [activeTab, setActiveTab] = useState<'send' | 'cross-chain'>('send');
const [selectedChain, setSelectedChain] = useState('');
const ibcChains: IbcChain[] = [
{
id: 'osmosis',
name: 'Osmosis',
icon: 'https://osmosis.zone/assets/icons/osmo-logo-icon.svg',
prefix: 'osmo',
},
];
const [selectedFromChain, setSelectedFromChain] = useState<IbcChain>(ibcChains[0]);
const [selectedToChain, setSelectedToChain] = useState<IbcChain>(ibcChains[1]);

useEffect(() => {
if (selectedFromChain && selectedToChain && selectedFromChain.id === selectedToChain.id) {
// If chains match, switch the destination chain to the other available chain
const otherChain = ibcChains.find(chain => chain.id !== selectedFromChain.id);
if (otherChain) {
setSelectedToChain(otherChain);
}
}
}, [selectedFromChain, selectedToChain, ibcChains]);

const getAvailableToChains = useMemo(() => {
return ibcChains.filter(chain => chain.id !== selectedFromChain.id);
}, [ibcChains, selectedFromChain]);

return (
<div className="rounded-2xl w-full ">
Expand All @@ -57,38 +102,50 @@ export default function SendBox({
>
Send
</button>
<button
aria-label="cross-chain-transfer-tab"
className={`flex-1 py-2 px-4 text-sm font-medium rounded-xl transition-colors ${
activeTab === 'cross-chain'
? 'dark:bg-[#FFFFFF1F] bg-[#FFFFFF] text-[#161616] dark:text-white'
: 'text-[#808080]'
}`}
onClick={() => setActiveTab('cross-chain')}
>
Cross-Chain Transfer
</button>
{env.chainTier === 'testnet' && (
<button
aria-label="cross-chain-transfer-tab"
className={`flex-1 py-2 px-4 text-sm font-medium rounded-xl transition-colors ${
activeTab === 'cross-chain'
? 'dark:bg-[#FFFFFF1F] bg-[#FFFFFF] text-[#161616] dark:text-white'
: 'text-[#808080]'
}`}
onClick={() => setActiveTab('cross-chain')}
>
Cross-Chain Transfer
</button>
)}
</div>

<div className="">
{isBalancesLoading || !balances ? (
<div className="skeleton h-[300px] w-full"></div>
) : (
<>
{activeTab === 'cross-chain' ? (
{activeTab === 'cross-chain' && env.chainTier === 'testnet' ? (
<IbcSendForm
isIbcTransfer={true}
ibcChains={ibcChains}
selectedChain={selectedChain}
setSelectedChain={setSelectedChain}
selectedFromChain={selectedFromChain}
setSelectedFromChain={setSelectedFromChain}
selectedToChain={selectedToChain}
setSelectedToChain={setSelectedToChain}
address={address}
destinationChain={selectedChain}
destinationChain={selectedToChain}
balances={balances}
isBalancesLoading={isBalancesLoading}
refetchBalances={refetchBalances}
refetchHistory={refetchHistory}
selectedDenom={selectedDenom}
osmosisBalances={osmosisBalances}
isGroup={isGroup}
admin={admin}
refetchProposals={refetchProposals}
isOsmosisBalancesLoading={isOsmosisBalancesLoading}
refetchOsmosisBalances={refetchOsmosisBalances}
resolveOsmosisRefetch={resolveOsmosisRefetch}
availableToChains={getAvailableToChains}
chains={chains}
/>
) : (
<SendForm
Expand Down
Loading

0 comments on commit 64be274

Please sign in to comment.