diff --git a/.vscode/launch.json b/.vscode/launch.json index d2b1ac87..6e0f4715 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -8,9 +8,9 @@ "name": "Debug Tests", "type": "node", "request": "launch", - "runtimeExecutable": "${workspaceRoot}/node_modules/.bin/react-scripts", + "runtimeExecutable": "${workspaceRoot}/app/node_modules/.bin/react-scripts", "args": ["test", "--runInBand", "--no-cache", "--watchAll=false"], - "cwd": "${workspaceRoot}", + "cwd": "${workspaceRoot}/app/", "protocol": "inspector", "console": "integratedTerminal", "internalConsoleOptions": "neverOpen", diff --git a/app/.storybook/preview.tsx b/app/.storybook/preview.tsx index 7faacfb7..992cf3ea 100644 --- a/app/.storybook/preview.tsx +++ b/app/.storybook/preview.tsx @@ -7,7 +7,7 @@ import { createActions } from '../src/action'; import { Background } from '../src/components/common/base'; import { ThemeProvider } from '../src/components/theme'; import { Store, StoreProvider } from '../src/store'; -import { sampleApiResponses } from '../src/util/sampleData'; +import { sampleApiResponses } from '../src/util/tests/sampleData'; /** * Create a store with dummy data to use for stories @@ -17,15 +17,14 @@ const store = new Store(); /** * Create dummy actions to use for stories */ + // mock the GRPC client to return sample data instead of making an actual request const grpc = { request: (methodDescriptor: any) => { const endpoint = `${methodDescriptor.service.serviceName}.${methodDescriptor.methodName}`; const data = sampleApiResponses[endpoint] || {}; // the calling function expects the return value to have a `toObject` function - const response: any = { - toObject: () => data, - }; + const response: any = { toObject: () => data }; return Promise.resolve(response); }, }; @@ -33,16 +32,30 @@ const actions = createActions(store, grpc); // execute actions to populate the store data with the sample API responses actions.node.getBalances(); +actions.swap.listSwaps(); +/** + * add the mobx store to Storybook parameters so that stories can manipulate it + */ addParameters({ store }); /** * decorator function to wrap all stories with the necessary providers */ -addDecorator(storyFn => ( +addDecorator((storyFn, ctx) => ( - {storyFn()} + {/* modify the bg styles so it isn't too big in docs mode */} + + {/* wrap the component in a centered div for small components */} + {ctx.parameters.centered ? ( +
+ {storyFn()} +
+ ) : ( + storyFn() + )} +
)); diff --git a/app/src/App.scss b/app/src/App.scss index d8617d53..307d70ac 100644 --- a/app/src/App.scss +++ b/app/src/App.scss @@ -11,6 +11,7 @@ @import '../node_modules/bootstrap/scss/buttons'; @import '../node_modules/bootstrap/scss/tables'; @import '../node_modules/bootstrap/scss/nav'; +@import '../node_modules/bootstrap/scss/grid'; @font-face { font-family: 'OpenSans Light'; diff --git a/app/src/App.tsx b/app/src/App.tsx index a940dc13..800e4fae 100644 --- a/app/src/App.tsx +++ b/app/src/App.tsx @@ -2,7 +2,7 @@ import React from 'react'; import './App.scss'; import { Store, StoreProvider } from 'store'; import { Layout } from 'components/layout'; -import SamplePage from 'components/pages/SamplePage'; +import LoopPage from 'components/loop/LoopPage'; import { ThemeProvider } from 'components/theme'; const App = () => { @@ -11,7 +11,7 @@ const App = () => { - + diff --git a/app/src/__mocks__/@improbable-eng/grpc-web.ts b/app/src/__mocks__/@improbable-eng/grpc-web.ts index 0fb5a62c..0f3f38fa 100644 --- a/app/src/__mocks__/@improbable-eng/grpc-web.ts +++ b/app/src/__mocks__/@improbable-eng/grpc-web.ts @@ -1,7 +1,7 @@ import { ProtobufMessage } from '@improbable-eng/grpc-web/dist/typings/message'; import { UnaryMethodDefinition } from '@improbable-eng/grpc-web/dist/typings/service'; import { UnaryRpcOptions } from '@improbable-eng/grpc-web/dist/typings/unary'; -import { sampleApiResponses } from 'util/sampleData'; +import { sampleApiResponses } from 'util/tests/sampleData'; // mock grpc module export const grpc = { diff --git a/app/src/__stories__/LoopHistory.stories.tsx b/app/src/__stories__/LoopHistory.stories.tsx new file mode 100644 index 00000000..ff670647 --- /dev/null +++ b/app/src/__stories__/LoopHistory.stories.tsx @@ -0,0 +1,27 @@ +import React from 'react'; +import { StoryContext } from '@storybook/addons'; +import { Store } from 'store'; +import Tile from 'components/common/Tile'; +import LoopHistory from 'components/loop/LoopHistory'; + +export default { + title: 'Loop History', + component: LoopHistory, + parameters: { centered: true }, +}; + +export const Default = (ctx: StoryContext) => { + // grab the store from the Storybook parameter defined in preview.tsx + const { swaps } = ctx.parameters.store as Store; + return ; +}; + +export const InsideTile = (ctx: StoryContext) => { + // grab the store from the Storybook parameter defined in preview.tsx + const { swaps } = ctx.parameters.store as Store; + return ( + + + + ); +}; diff --git a/app/src/__stories__/LoopPage.stories.tsx b/app/src/__stories__/LoopPage.stories.tsx new file mode 100644 index 00000000..e3cf7e48 --- /dev/null +++ b/app/src/__stories__/LoopPage.stories.tsx @@ -0,0 +1,9 @@ +import React from 'react'; +import LoopPage from 'components/loop/LoopPage'; + +export default { + title: 'Loop Page', + component: LoopPage, +}; + +export const Default = () => ; diff --git a/app/src/__stories__/NodeStatus.stories.tsx b/app/src/__stories__/NodeStatus.stories.tsx index c534f370..a216af8b 100644 --- a/app/src/__stories__/NodeStatus.stories.tsx +++ b/app/src/__stories__/NodeStatus.stories.tsx @@ -1,9 +1,31 @@ -import React from 'react'; +import React, { useEffect } from 'react'; +import { StoryContext } from '@storybook/addons'; +import { Store } from 'store'; import NodeStatus from 'components/NodeStatus'; export default { title: 'Node Status', component: NodeStatus, + parameters: { centered: true }, }; -export const Default = () => ; +export const Default = (ctx: StoryContext) => { + useEffect(() => { + // grab the store from the Storybook parameter defined in preview.tsx + const store = ctx.parameters.store as Store; + const { channelBalance, walletBalance } = store.balances || { + channelBalance: 0, + walletBalance: 0, + }; + store.balances = { channelBalance: 0, walletBalance: 0 }; + + // change back to sample data when the component is unmounted + return () => { + store.balances = { channelBalance, walletBalance }; + }; + }, []); + + return ; +}; + +export const WithBalances = () => ; diff --git a/app/src/__stories__/Tile.stories.tsx b/app/src/__stories__/Tile.stories.tsx new file mode 100644 index 00000000..bbfc24c2 --- /dev/null +++ b/app/src/__stories__/Tile.stories.tsx @@ -0,0 +1,27 @@ +import React from 'react'; +import { action } from '@storybook/addon-actions'; +import Tile from 'components/common/Tile'; + +export default { + title: 'Tile', + component: Tile, + parameters: { centered: true }, +}; + +export const Empty = () => ; + +export const WithText = () => ; + +export const WithChildren = () => ( + Sample child content +); + +export const WithArrowIcon = () => ( + action('ArrowIcon')}> + Sample Text + +); + +export const InboundLiquidity = () => ( + +); diff --git a/app/src/__tests__/App.spec.tsx b/app/src/__tests__/App.spec.tsx index 7eb09757..fee63a18 100644 --- a/app/src/__tests__/App.spec.tsx +++ b/app/src/__tests__/App.spec.tsx @@ -4,6 +4,6 @@ import App from '../App'; it('renders the App', () => { const { getByText } = render(); - const linkElement = getByText('Node Info'); + const linkElement = getByText('Node Status'); expect(linkElement).toBeInTheDocument(); }); diff --git a/app/src/__tests__/action/node.spec.ts b/app/src/__tests__/action/node.spec.ts index d708021f..11cbcbf8 100644 --- a/app/src/__tests__/action/node.spec.ts +++ b/app/src/__tests__/action/node.spec.ts @@ -1,4 +1,4 @@ -import { lndChannelBalance, lndWalletBalance } from 'util/sampleData'; +import { lndChannelBalance, lndWalletBalance } from 'util/tests/sampleData'; import NodeAction from 'action/node'; import { GrpcClient, LndApi } from 'api'; import { Store } from 'store'; diff --git a/app/src/__tests__/action/store.spec.ts b/app/src/__tests__/action/store.spec.ts new file mode 100644 index 00000000..a83d40e9 --- /dev/null +++ b/app/src/__tests__/action/store.spec.ts @@ -0,0 +1,32 @@ +import { lndListChannels } from 'util/tests/sampleData'; +import { createActions, StoreActions } from 'action'; +import { Store } from 'store'; + +describe('SwapAction', () => { + let store: Store; + let actions: StoreActions; + + beforeEach(() => { + store = new Store(); + actions = createActions(store); + }); + + it('should compute inbound liquidity', async () => { + const inbound = lndListChannels.channelsList.reduce( + (sum, chan) => sum + chan.remoteBalance, + 0, + ); + + await actions.channel.getChannels(); + expect(store.totalInbound).toBe(inbound); + }); + + it('should compute outbound liquidity', async () => { + const outbound = lndListChannels.channelsList.reduce( + (sum, chan) => sum + chan.localBalance, + 0, + ); + await actions.channel.getChannels(); + expect(store.totalOutbound).toBe(outbound); + }); +}); diff --git a/app/src/__tests__/components/common/Tile.spec.tsx b/app/src/__tests__/components/common/Tile.spec.tsx new file mode 100644 index 00000000..6b36dd7e --- /dev/null +++ b/app/src/__tests__/components/common/Tile.spec.tsx @@ -0,0 +1,38 @@ +import React, { ReactNode } from 'react'; +import { fireEvent } from '@testing-library/react'; +import { renderWithProviders } from 'util/tests'; +import Tile from 'components/common/Tile'; + +describe('Tile component', () => { + const handleArrowClick = jest.fn(); + + const render = (text?: string, children?: ReactNode) => { + const cmp = ( + + {children} + + ); + return renderWithProviders(cmp); + }; + + it('should display the title', () => { + const { getByText } = render(); + expect(getByText('Test Tile')).toBeInTheDocument(); + }); + + it('should display the text', () => { + const { getByText } = render('test text'); + expect(getByText('test text')).toBeInTheDocument(); + }); + + it('should display child components', () => { + const { getByText } = render(undefined, 'test child'); + expect(getByText('test child')).toBeInTheDocument(); + }); + + it('should handle the arrow click event', () => { + const { getByText } = render(); + fireEvent.click(getByText('arrow-right.svg')); + expect(handleArrowClick).toBeCalled(); + }); +}); diff --git a/app/src/__tests__/components/loop/LoopPage.spec.tsx b/app/src/__tests__/components/loop/LoopPage.spec.tsx new file mode 100644 index 00000000..4e03746f --- /dev/null +++ b/app/src/__tests__/components/loop/LoopPage.spec.tsx @@ -0,0 +1,43 @@ +import React from 'react'; +import { SwapStatus } from 'types/generated/loop_pb'; +import { renderWithProviders } from 'util/tests'; +import { loopListSwaps } from 'util/tests/sampleData'; +import LoopPage from 'components/loop/LoopPage'; + +describe('LoopPage component', () => { + const render = () => { + return renderWithProviders(); + }; + + it('should display the page title', () => { + const { getByText } = render(); + expect(getByText('Lightning Loop')).toBeInTheDocument(); + }); + + it('should display the three tiles', () => { + const { getByText } = render(); + expect(getByText('Loop History')).toBeInTheDocument(); + expect(getByText('Total Inbound Liquidity')).toBeInTheDocument(); + expect(getByText('Total Outbound Liquidity')).toBeInTheDocument(); + }); + + it('should display the liquidity numbers', async () => { + const { findByText } = render(); + // these values are defined in sampleData.ts + expect(await findByText('4,501,409 SAT')).toBeInTheDocument(); + expect(await findByText('9,988,660 SAT')).toBeInTheDocument(); + }); + + it('should display the loop history records', async () => { + const { findByText } = render(); + // convert from numeric timestamp to string (1586390353623905000 -> '4/15/2020') + const formatDate = (s: SwapStatus.AsObject) => + new Date(s.initiationTime / 1000 / 1000).toLocaleDateString(); + const [swap1, swap2] = loopListSwaps.swapsList; + + expect(await findByText(formatDate(swap1))).toBeInTheDocument(); + expect(await findByText('530,000 SAT')).toBeInTheDocument(); + expect(await findByText(formatDate(swap2))).toBeInTheDocument(); + expect(await findByText('525,000 SAT')).toBeInTheDocument(); + }); +}); diff --git a/app/src/action/swap.ts b/app/src/action/swap.ts index 102f8b01..1a6b60fc 100644 --- a/app/src/action/swap.ts +++ b/app/src/action/swap.ts @@ -29,7 +29,7 @@ class SwapAction { .map(s => ({ id: s.id, type: this._typeToString(s.type), - amount: BigInt(s.amt), + amount: s.amt, createdOn: new Date(s.initiationTime / 1000 / 1000), status: this._stateToString(s.state), })); diff --git a/app/src/assets/icons/arrow-right.svg b/app/src/assets/icons/arrow-right.svg new file mode 100644 index 00000000..d199a0db --- /dev/null +++ b/app/src/assets/icons/arrow-right.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/app/src/components/common/Tile.tsx b/app/src/components/common/Tile.tsx new file mode 100644 index 00000000..f3ffcd3a --- /dev/null +++ b/app/src/components/common/Tile.tsx @@ -0,0 +1,61 @@ +import React from 'react'; +import { styled } from 'components/theme'; +import { ArrowRight } from './icons'; +import { Title } from './text'; + +const Styled = { + TileWrap: styled.div` + min-height: 105px; + padding: 15px; + background-color: ${props => props.theme.colors.tileBack}; + border-radius: 4px; + `, + Header: styled.div` + display: flex; + justify-content: space-between; + `, + ArrowIcon: styled(ArrowRight)` + width: 16px; + margin-top: -5px; + cursor: pointer; + `, + Text: styled.div` + font-size: ${props => props.theme.sizes.xl}; + line-height: 37px; + letter-spacing: 0.43px; + margin-top: 10px; + `, +}; + +interface Props { + /** + * the title to display in the tile + */ + title: string; + /** + * optional text to display in the tile. if this is not + * provided, then the `children` will be displayed instead + */ + text?: string; + /** + * optional click handler for the arrow which will not be + * visible if this prop is not defined + */ + onArrowClick?: () => void; +} + +const Tile: React.FC = ({ title, text, onArrowClick, children }) => { + const { TileWrap, Header, ArrowIcon, Text } = Styled; + + return ( + +
+ {title} + {onArrowClick && } +
+ {text ? {text} : children} +
+ ); +}; + +export default Tile; diff --git a/app/src/components/common/grid.tsx b/app/src/components/common/grid.tsx new file mode 100644 index 00000000..ec56ebad --- /dev/null +++ b/app/src/components/common/grid.tsx @@ -0,0 +1,18 @@ +import React from 'react'; + +/** + * This component represents a Row in the bootstrap Grid layout + */ +export const Row: React.FC = ({ children }) =>
{children}
; + +/** + * A column in the bootstrap Grid layout + * @param cols the number of columns wide (optional) + */ +export const Column: React.FC<{ + cols?: number; + className?: string; +}> = ({ cols, children, className }) => { + const cls = (className || '') + (cols ? ` col-${cols}` : ' col'); + return
{children}
; +}; diff --git a/app/src/components/common/icons.tsx b/app/src/components/common/icons.tsx index 10e928c0..97d15836 100644 --- a/app/src/components/common/icons.tsx +++ b/app/src/components/common/icons.tsx @@ -1,3 +1,4 @@ export { ReactComponent as Bolt } from 'assets/icons/bolt.svg'; export { ReactComponent as Bitcoin } from 'assets/icons/bitcoin.svg'; export { ReactComponent as Menu } from 'assets/icons/menu.svg'; +export { ReactComponent as ArrowRight } from 'assets/icons/arrow-right.svg'; diff --git a/app/src/components/common/text.tsx b/app/src/components/common/text.tsx index 351eb610..2515ed29 100644 --- a/app/src/components/common/text.tsx +++ b/app/src/components/common/text.tsx @@ -30,3 +30,12 @@ export const XLargeText = styled.span` font-size: ${props => props.theme.sizes.xl}; letter-spacing: 0.43px; `; + +export const PageTitle = styled.h2` + font-family: ${props => props.theme.fonts.light}; + font-size: ${props => props.theme.sizes.l}; + text-align: center; + text-transform: uppercase; + letter-spacing: 2.7px; + line-height: 30px; +`; diff --git a/app/src/components/layout/Layout.tsx b/app/src/components/layout/Layout.tsx index 74a1dc80..6d7d2ebd 100644 --- a/app/src/components/layout/Layout.tsx +++ b/app/src/components/layout/Layout.tsx @@ -14,7 +14,8 @@ const Styled = { Container: styled.div` position: relative; min-height: 100vh; - width: 1440px; + max-width: 1440px; + width: 100%; margin: 0 auto; `, MenuIcon: styled(Menu)` diff --git a/app/src/components/loop/LoopHistory.tsx b/app/src/components/loop/LoopHistory.tsx new file mode 100644 index 00000000..9dcfa724 --- /dev/null +++ b/app/src/components/loop/LoopHistory.tsx @@ -0,0 +1,40 @@ +import React from 'react'; +import { Swap } from 'types/state'; +import { Column, Row } from 'components/common/grid'; +import { SmallText } from 'components/common/text'; +import { styled } from 'components/theme'; + +const Styled = { + RightColumn: styled(Column)` + text-align: right; + `, + SmallText: styled(SmallText)` + line-height: 1; + `, +}; + +interface Props { + swaps: Swap[]; +} + +const LoopHistory: React.FC = ({ swaps }) => { + const recentSwaps = swaps.slice(0, 2); + + const { RightColumn, SmallText } = Styled; + return ( + <> + {recentSwaps.map(swap => ( + + + {swap.createdOn.toLocaleDateString()} + + + {`${swap.amount.toLocaleString()} SAT`} + + + ))} + + ); +}; + +export default LoopHistory; diff --git a/app/src/components/loop/LoopPage.tsx b/app/src/components/loop/LoopPage.tsx new file mode 100644 index 00000000..6f0beb8f --- /dev/null +++ b/app/src/components/loop/LoopPage.tsx @@ -0,0 +1,63 @@ +import React, { useEffect } from 'react'; +import { observer } from 'mobx-react-lite'; +import { usePrefixedTranslation } from 'hooks'; +import { useActions, useStore } from 'store'; +import { Column, Row } from 'components/common/grid'; +import { PageTitle } from 'components/common/text'; +import Tile from 'components/common/Tile'; +import { styled } from 'components/theme'; +import LoopHistory from './LoopHistory'; + +const Styled = { + PageWrap: styled.div` + padding: 40px 50px; + `, + TileSection: styled.section` + margin-top: 90px; + `, +}; + +const LoopPage: React.FC = () => { + const store = useStore(); + const { node, channel, swap } = useActions(); + const { l } = usePrefixedTranslation('cmps.loop.LoopPage'); + + useEffect(() => { + // fetch RPC data when the component mounts if there is no + if (store.channels.length === 0) { + channel.getChannels(); + node.getBalances(); + swap.listSwaps(); + } + }, [store, node, channel, swap]); + + const { PageWrap, TileSection } = Styled; + return ( + + {l('pageTitle')} + + + + null}> + + + + + + + + + + + + + ); +}; + +export default observer(LoopPage); diff --git a/app/src/components/pages/SamplePage.tsx b/app/src/components/pages/SamplePage.tsx deleted file mode 100644 index 0f80eb76..00000000 --- a/app/src/components/pages/SamplePage.tsx +++ /dev/null @@ -1,129 +0,0 @@ -import React, { useEffect } from 'react'; -import { observer } from 'mobx-react-lite'; -import { usePrefixedTranslation } from 'hooks'; -import { useActions, useStore } from 'store/provider'; - -const SamplePage: React.FC = () => { - const store = useStore(); - const { node, channel, swap } = useActions(); - const { l } = usePrefixedTranslation('App'); - useEffect(() => { - // fetch node info when the component is mounted - const fetchInfo = async () => { - try { - await node.getInfo(); - await node.getBalances(); - } catch (error) { - console.log('Failed to fetch node info', error); - } - }; - - fetchInfo(); - }, [node]); - - const balances = store.balances || { channelBalance: 0, walletBalance: 0 }; - - return ( - <> -
-

Lightning Loop

-
-
-

{l('App.nodeInfo')}

- {store.info && ( - - - - - - - - - - - - - - - - - - - - - - - -
{l('pubkey')}{store.info.identityPubkey}
{l('alias')}{store.info.alias}
{l('version')}{store.info.version}
{l('numChannels')}{store.info.numActiveChannels}
{l('balances')}{`${balances.channelBalance} in channels, ${balances.walletBalance} in wallet`}
- )} -
-
-

- {store.channels.length} Channels - -

- - - - - - - - - - - - - - {store.channels.map(c => ( - - - - - - - - - - ))} - -
Can ReceiveCan SendIn Fee %Up time %Volume (24h)Peer/AliasCapacity
{c.remoteBalance}{c.localBalance}{c.uptime}{c.remotePubkey.substring(0, 12)}{c.capacity}
-
-
-

- {store.swaps.length} Swaps - -

- - - - - - - - - - - {store.swaps.map(s => ( - - - - - - - ))} - -
DateTypeAmountStatus
{s.createdOn.toString()}{s.type}{s.amount.toString()}{s.status}
-
- - ); -}; - -export default observer(SamplePage); diff --git a/app/src/components/theme.tsx b/app/src/components/theme.tsx index ccfcf5ed..da65ee9f 100644 --- a/app/src/components/theme.tsx +++ b/app/src/components/theme.tsx @@ -23,6 +23,7 @@ const theme = { white: '#ffffff', whitish: '#f5f5f5', pink: '#f5406e', + tileBack: 'rgba(245,245,245,0.04)', }, }; diff --git a/app/src/i18n/locales/en-US.json b/app/src/i18n/locales/en-US.json index 01a3edc2..394d7205 100644 --- a/app/src/i18n/locales/en-US.json +++ b/app/src/i18n/locales/en-US.json @@ -1,4 +1,8 @@ { + "cmps.loop.LoopPage.pageTitle": "Lightning Loop", + "cmps.loop.LoopPage.history": "Loop History", + "cmps.loop.LoopPage.inbound": "Total Inbound Liquidity", + "cmps.loop.LoopPage.outbound": "Total Outbound Liquidity", "App.nodeInfo": "Node Info", "App.pubkey": "Pubkey", "App.alias": "Alias", diff --git a/app/src/store/index.ts b/app/src/store/index.ts index b880e431..43fe688a 100644 --- a/app/src/store/index.ts +++ b/app/src/store/index.ts @@ -1,17 +1,40 @@ -import { observable } from 'mobx'; +import { computed, observable } from 'mobx'; import { Channel, NodeBalances, NodeInfo, Swap } from 'types/state'; /** * The store used to manage global app state */ export class Store { + // // App state + // @observable sidebarCollapsed = false; + + // // API data + // @observable info?: NodeInfo = undefined; @observable balances?: NodeBalances = undefined; @observable channels: Channel[] = []; @observable swaps: Swap[] = []; + + // + // computed data + // + + /** + * the sum of remote balance of all channels + */ + @computed get totalInbound() { + return this.channels.reduce((sum, chan) => sum + chan.remoteBalance, 0); + } + + /** + * the sum of local balance of all channels + */ + @computed get totalOutbound() { + return this.channels.reduce((sum, chan) => sum + chan.localBalance, 0); + } } // re-export from provider diff --git a/app/src/types/state.ts b/app/src/types/state.ts index e4157e65..d76f6833 100644 --- a/app/src/types/state.ts +++ b/app/src/types/state.ts @@ -25,7 +25,7 @@ export interface Channel { export interface Swap { id: string; type: string; - amount: BigInt; + amount: number; createdOn: Date; status: string; } diff --git a/app/src/util/sampleData.ts b/app/src/util/tests/sampleData.ts similarity index 95% rename from app/src/util/sampleData.ts rename to app/src/util/tests/sampleData.ts index e7f3a82d..d04c4baa 100644 --- a/app/src/util/sampleData.ts +++ b/app/src/util/tests/sampleData.ts @@ -92,12 +92,12 @@ export const lndListChannels: LND.ListChannelsResponse.AsObject = { export const loopListSwaps: LOOP.ListSwapsResponse.AsObject = { swapsList: [...Array(7)].map((x, i) => ({ - amt: 500000, - id: 'f4eb118383c2b09d8c7289ce21c25900cfb4545d46c47ed23a31ad2aa57ce835', + amt: 500000 + i * 5000, + id: `f4eb118383c2b09d8c7289ce21c25900cfb4545d46c47ed23a31ad2aa57ce83${i}`, idBytes: '9OsRg4PCsJ2MconOIcJZAM+0VF1GxH7SOjGtKqV86DU=', type: (i % 3) as any, state: i as any, - initiationTime: 1586390353623905000, + initiationTime: 1586390353623905000 + i * 100000000000000, lastUpdateTime: 1586398369729857000, htlcAddress: 'bcrt1qzu4077erkr78k52yuf2rwkk6ayr6m3wtazdfz2qqmd7taa5vvy9s5d75gd', costServer: 66,