Skip to content

Commit a70de02

Browse files
committed
wip: feat-payment-channel
1 parent c36e56c commit a70de02

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

68 files changed

+7114
-3423
lines changed

packages/neuron-ui/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@
4747
"displayName": "UI Tests"
4848
},
4949
"dependencies": {
50+
"@ckb-connect/perun-wallet-wrapper": "1.0.5",
5051
"@ckb-lumos/base": "0.23.0",
5152
"@ckb-lumos/bi": "0.23.0",
5253
"@ckb-lumos/codec": "0.23.0",
Lines changed: 130 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,130 @@
1+
import React, { useCallback, useEffect, useState } from 'react'
2+
import { showPageNotice, useDispatch } from 'states'
3+
import { openExternal, perunServiceAction, showErrorMessage } from 'services/remote'
4+
5+
import {
6+
clsx,
7+
equalNumPaddedHex,
8+
getExplorerUrl,
9+
isSuccessResponse,
10+
localNumberFormatter,
11+
RoutePath,
12+
useLocalDescription,
13+
} from 'utils'
14+
import { TableProps } from 'widgets/Table'
15+
import { useNavigate } from 'react-router-dom'
16+
import { ExplorerIcon, Copy, DetailIcon } from 'widgets/Icons/icon'
17+
import { useTranslation } from 'react-i18next'
18+
import Dialog from 'widgets/Dialog'
19+
import TextField from 'widgets/TextField'
20+
import ShowOrEditDesc from 'widgets/ShowOrEditDesc'
21+
import Tooltip from 'widgets/Tooltip'
22+
import AmendPendingTransactionDialog from 'components/AmendPendingTransactionDialog'
23+
import { getTransaction as getOnChainTransaction } from 'services/chain'
24+
25+
import Button from 'widgets/Button'
26+
import styles from './perun.module.scss'
27+
28+
const RowExtend = ({ channel }: { channel: State.PerunChannel }) => {
29+
const dispatch = useDispatch()
30+
const [t] = useTranslation()
31+
const [updateChannelDialog, setUpdateChannelDialog] = useState(false)
32+
const [updateAmount, setUpdateAmount] = useState<number>(0)
33+
34+
const handleCloseChannel = async (channelId: string) => {
35+
console.log('handleCloseChannel-----', channelId)
36+
const res = await perunServiceAction({
37+
type: 'close',
38+
payload: {
39+
channelId,
40+
},
41+
})
42+
43+
if (!isSuccessResponse(res)) {
44+
// handleRejected(res.message as string)
45+
return
46+
}
47+
console.log('handleCloseChannel-----res-----', res)
48+
// channels.delete(channelIdToString(res.result.channelId))
49+
}
50+
51+
const handleUpdateChannel = async (channelId: string, swapAmount: number) => {
52+
console.log('handleUpdateChannel-----', channelId, swapAmount)
53+
54+
const res = await perunServiceAction({
55+
type: 'update',
56+
payload: {
57+
channelId,
58+
index: 0,
59+
amount: equalNumPaddedHex(BigInt(swapAmount * 1e8)),
60+
},
61+
})
62+
console.log('HANDLE UPDATE CHANNEL RES: ', res)
63+
if (!isSuccessResponse(res)) {
64+
// handleRejected(res.message as string)
65+
return
66+
}
67+
// If we could update the chanenl, we cache the updated channel state.
68+
const updatedChannelState = res.result.state
69+
console.log('updatedChannelState-----', updatedChannelState)
70+
// const channel = channels.get(channelIdToString(channelId))
71+
if (!channel) {
72+
showErrorMessage('Error', `channel ${channelId} not found`)
73+
// return
74+
}
75+
// channel.version = updatedChannelState.version
76+
// channel.allocation = updatedChannelState.allocation
77+
// channel.isFinal = updatedChannelState.isFinal
78+
}
79+
80+
const handleUpdateAmountChange = (am: string) => {
81+
const amountNum = parseFloat(am)
82+
if (Number.isNaN(amountNum)) {
83+
return
84+
}
85+
setUpdateAmount(amountNum)
86+
}
87+
88+
return (
89+
<div>
90+
<Button type="text" onClick={() => handleCloseChannel(channel.channelId)}>
91+
Close
92+
</Button>
93+
<Button type="text" onClick={() => setUpdateChannelDialog(true)}>
94+
Update Channel
95+
</Button>
96+
97+
<Dialog
98+
show={updateChannelDialog}
99+
showFooter={false}
100+
onClose={() => {
101+
setUpdateChannelDialog(false)
102+
}}
103+
>
104+
<p>{t(`perun.enter-amount`)}</p>
105+
<div className={styles.formGroup}>
106+
<p className={styles.formLabel}>Amount:</p>
107+
<TextField
108+
type="number"
109+
value={updateAmount || ''}
110+
onChange={(event: { currentTarget: { value: string } }) => {
111+
handleUpdateAmountChange(event.currentTarget.value)
112+
}}
113+
/>
114+
<span className={styles.formText}>CKB</span>
115+
</div>
116+
<Button
117+
className={styles.updateButton}
118+
onClick={() => {
119+
setUpdateChannelDialog(false)
120+
handleUpdateChannel(channel.channelId, updateAmount!)
121+
}}
122+
>
123+
{t(`perun.update-channel`)}
124+
</Button>
125+
</Dialog>
126+
</div>
127+
)
128+
}
129+
130+
export default RowExtend
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
import { useCallback } from 'react'
2+
3+
export function usePerunChannel() {
4+
const openChannel = useCallback(() => {
5+
// TODO: Implement channel opening logic
6+
}, [])
7+
8+
return {
9+
openChannel,
10+
}
11+
}
12+
13+
export function useFiberChannel() {
14+
const openChannel = useCallback(() => {
15+
// TODO: Implement channel opening logic
16+
}, [])
17+
18+
return {
19+
openChannel,
20+
}
21+
}
Lines changed: 238 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,238 @@
1+
import React, { useState, useEffect, useCallback } from 'react'
2+
import { useTranslation } from 'react-i18next'
3+
import { useState as useGlobalState } from 'states'
4+
import { bytes } from '@ckb-lumos/codec'
5+
import { blockchain } from '@ckb-lumos/base'
6+
import Dialog from 'widgets/Dialog'
7+
import Table, { TableProps } from 'widgets/Table'
8+
import { Download, Search, ArrowNext, Clean, ArrowUp, ArrowDown } from 'widgets/Icons/icon'
9+
import Button from 'widgets/Button'
10+
import Switch from 'widgets/Switch'
11+
import Tooltip from 'widgets/Tooltip'
12+
import PageContainer from 'components/PageContainer'
13+
import {
14+
PerunIcon,
15+
AddSimple,
16+
DetailIcon,
17+
CkbIcon,
18+
InfoCircleOutlined,
19+
DepositTimeSort,
20+
PerunSend,
21+
PerunClose,
22+
LineDownArrow,
23+
} from 'widgets/Icons/icon'
24+
import TableNoData from 'widgets/Icons/TableNoData.png'
25+
import { type CKBComponents } from '@ckb-lumos/lumos/rpc'
26+
import {
27+
SerializeOffChainParticipant,
28+
SerializeSEC1EncodedPubKey,
29+
} from '@ckb-connect/perun-wallet-wrapper/dist/ckb/serialization'
30+
import { channelIdToString, channelIdFromString } from '@ckb-connect/perun-wallet-wrapper/dist/translator'
31+
import * as wire from '@ckb-connect/perun-wallet-wrapper/dist/wire'
32+
33+
import { ControllerResponse } from 'services/remote/remoteApiWrapper'
34+
import {
35+
OfflineSignStatus,
36+
OfflineSignType,
37+
getCurrentWalletAccountExtendedPubKey,
38+
perunServiceAction,
39+
respondPerunRequest,
40+
signRawMessage,
41+
signTransactionOnly,
42+
showErrorMessage,
43+
} from 'services/remote'
44+
import {
45+
addressToScript,
46+
scriptToAddress,
47+
bytesToHex,
48+
ErrorCode,
49+
errorFormatter,
50+
isSuccessResponse,
51+
clsx,
52+
getParticipantByAddressAndPubkey,
53+
} from 'utils'
54+
import { PasswordDialog } from 'components/SignAndVerify'
55+
import PerunCreationRequestList from 'components/PerunCreationRequestList'
56+
import PerunLockedInChannels from 'components/PerunLockedInChannels'
57+
import PerunCloseChannel from 'components/PerunCloseChannel'
58+
import PerunOpenChannel from 'components/PerunOpenChannel'
59+
import PerunSendPayment from 'components/PerunSendPayment'
60+
import { State } from '@ckb-connect/perun-wallet-wrapper/wire'
61+
import RowExtend from './RowExtend'
62+
import styles from './perun.module.scss'
63+
64+
enum DialogType {
65+
creationRequest = 'creationRequest',
66+
lockedInChannels = 'lockedInChannels',
67+
closeChannel = 'closeChannel',
68+
send = 'send',
69+
openChannel = 'openChannel',
70+
}
71+
72+
const PaymentChannel = () => {
73+
const {
74+
wallet,
75+
perun: { requests, channels },
76+
} = useGlobalState()
77+
const [t, _] = useTranslation()
78+
const [dialogType, setDialogType] = useState<DialogType | undefined>(undefined)
79+
80+
const [myPubKey, setMyPubKey] = useState<string>('')
81+
82+
const [expandedRow, setExpandedRow] = useState<number | null>(null)
83+
84+
const assets = ['CKB']
85+
86+
useEffect(() => {
87+
getCurrentWalletAccountExtendedPubKey({ type: 0, index: 0 }).then(res => {
88+
if (isSuccessResponse(res)) {
89+
setMyPubKey(res.result)
90+
}
91+
})
92+
}, [])
93+
94+
const handleExpandClick = (idx: number | null) => {
95+
setExpandedRow(prevIndex => (prevIndex === idx ? null : idx))
96+
}
97+
98+
const columns: TableProps<State.Transaction>['columns'] = [
99+
{
100+
title: t('history.table.asset'),
101+
dataIndex: 'allocation',
102+
align: 'left',
103+
minWidth: '150px',
104+
render: (_, __, item) => JSON.stringify(item.allocation),
105+
},
106+
{
107+
title: t('perun.creation-time'),
108+
dataIndex: 'createdAt',
109+
align: 'left',
110+
minWidth: '150px',
111+
render: (_, __, item) => item.createdAt,
112+
sortable: true,
113+
},
114+
{
115+
title: t('history.table.status'),
116+
dataIndex: 'status',
117+
align: 'left',
118+
minWidth: '50px',
119+
render(_, __, item) {
120+
return 'status'
121+
},
122+
},
123+
{
124+
title: t('history.table.operation'),
125+
dataIndex: 'operation',
126+
align: 'center',
127+
minWidth: '72px',
128+
render(_, idx) {
129+
return <ArrowNext className={styles.arrow} data-is-expand-show={expandedRow === idx} />
130+
},
131+
},
132+
]
133+
134+
return (
135+
<PageContainer
136+
head={
137+
<div className={styles.pageHeader}>
138+
<PerunIcon />
139+
<p>{t('navbar.perun')}</p>
140+
</div>
141+
}
142+
>
143+
<div className={styles.container}>
144+
<div className={styles.topWrap}>
145+
<div className={clsx(styles.panel, styles.leftWrap)}>
146+
<h2>{t('perun.of-open-channels')}</h2>
147+
<h1>{channels.length}</h1>
148+
<Button type="primary" className={styles.createBtn} onClick={() => setDialogType(DialogType.openChannel)}>
149+
<AddSimple />
150+
{t('perun.create-new-channel')}
151+
</Button>
152+
</div>
153+
<div className={clsx(styles.panel, styles.rightWrap)}>
154+
<h2>
155+
{t('perun.locked-in-channels')}{' '}
156+
<Button
157+
type="text"
158+
className={styles.detailBtn}
159+
onClick={() => setDialogType(DialogType.lockedInChannels)}
160+
>
161+
<DetailIcon />
162+
</Button>
163+
</h2>
164+
<div className={styles.sliderWrap}>
165+
{assets.map(item => (
166+
<div className={styles.sliderItem} key={item}>
167+
<h2>
168+
<CkbIcon />
169+
{item}
170+
</h2>
171+
<p>0</p>
172+
</div>
173+
))}
174+
</div>
175+
</div>
176+
</div>
177+
178+
<div className={styles.panel}>
179+
<div className={styles.creationRequest}>
180+
<h2 className={styles.title}>
181+
{t('perun.channel-creation-request')}{' '}
182+
{requests.length > 0 && <span className={styles.badge}>{requests.length}</span>}
183+
</h2>
184+
<Button type="primary" onClick={() => setDialogType(DialogType.creationRequest)}>
185+
{t('perun.check')}
186+
</Button>
187+
</div>
188+
</div>
189+
190+
<div className={clsx(styles.panel, styles.overview)}>
191+
<div className={styles.header}>
192+
<h2>
193+
{t('perun.channel-overview')}
194+
<Tooltip tip={t('perun.channel-overview-tooltip')} showTriangle placement="top">
195+
<InfoCircleOutlined />
196+
</Tooltip>
197+
</h2>
198+
</div>
199+
200+
<div className={styles.overviewContent}>
201+
<Table
202+
columns={columns}
203+
dataSource={channels}
204+
noDataContent={t('overview.no-recent-activities')}
205+
rowExtendRender={channel => <RowExtend channel={channel} />}
206+
expandedRow={expandedRow}
207+
onRowClick={(_, __, idx) => handleExpandClick(idx)}
208+
/>
209+
</div>
210+
</div>
211+
212+
{dialogType === DialogType.creationRequest && requests.length > 0 && (
213+
<PerunCreationRequestList
214+
walletID={wallet?.id ?? ''}
215+
requests={requests}
216+
onCancel={() => setDialogType(undefined)}
217+
/>
218+
)}
219+
{dialogType === DialogType.lockedInChannels && (
220+
<PerunLockedInChannels onClose={() => setDialogType(undefined)} />
221+
)}
222+
{dialogType === DialogType.closeChannel && <PerunCloseChannel onClose={() => setDialogType(undefined)} />}
223+
224+
<PerunOpenChannel
225+
show={dialogType === DialogType.openChannel && myPubKey}
226+
onClose={() => setDialogType(undefined)}
227+
myPubKey={myPubKey}
228+
/>
229+
230+
{dialogType === DialogType.send && <PerunSendPayment onClose={() => setDialogType(undefined)} />}
231+
</div>
232+
</PageContainer>
233+
)
234+
}
235+
236+
PaymentChannel.displayName = 'PaymentChannel'
237+
238+
export default PaymentChannel

0 commit comments

Comments
 (0)