Merge pull request #286 from XRPLF/feat/tx-params-ui

HookParameters UI for transactions.
This commit is contained in:
muzamil
2023-03-06 16:06:19 +05:30
committed by GitHub
7 changed files with 232 additions and 164 deletions

View File

@@ -38,7 +38,8 @@ const Select = forwardRef<any, Props>((props, ref) => {
container: provided => {
return {
...provided,
position: 'relative'
position: 'relative',
width: '100%'
}
},
singleValue: provided => ({

View File

@@ -20,6 +20,7 @@ import { TxUI } from './ui'
import { default as _estimateFee } from '../../utils/estimateFee'
import toast from 'react-hot-toast'
import { combineFlags, extractFlags, transactionFlags } from '../../state/constants/flags'
import { SetHookData, toHex } from '../../utils/setHook'
export interface TransactionProps {
header: string
@@ -40,16 +41,30 @@ const Transaction: FC<TransactionProps> = ({ header, state: txState, ...props })
const prepareOptions = useCallback(
(state: Partial<TransactionState> = txState) => {
const { selectedTransaction, selectedDestAccount, selectedAccount, txFields, selectedFlags } =
state
const {
selectedTransaction,
selectedDestAccount,
selectedAccount,
txFields,
selectedFlags,
hookParameters
} = state
const TransactionType = selectedTransaction?.value || null
const Destination = selectedDestAccount?.value || txFields?.Destination
const Account = selectedAccount?.value || null
const Flags = combineFlags(selectedFlags?.map(flag => flag.value)) || txFields?.Flags
const HookParameters = Object.entries(hookParameters || {}).reduce<
SetHookData['HookParameters']
>((acc, [_, { label, value }]) => {
return acc.concat({
HookParameter: { HookParameterName: toHex(label), HookParameterValue: toHex(value) }
})
}, [])
return prepareTransaction({
...txFields,
HookParameters,
Flags,
TransactionType,
Destination,

View File

@@ -1,4 +1,4 @@
import { FC, useCallback, useEffect, useMemo, useState } from 'react'
import { FC, ReactNode, useCallback, useEffect, useMemo, useState } from 'react'
import Container from '../Container'
import Flex from '../Flex'
import Input from '../Input'
@@ -18,6 +18,7 @@ import { streamState } from '../DebugStream'
import { Button } from '..'
import Textarea from '../Textarea'
import { getFlags } from '../../state/constants/flags'
import { Plus, Trash } from 'phosphor-react'
interface UIProps {
setState: (pTx?: Partial<TransactionState> | undefined) => TransactionState | undefined
@@ -28,8 +29,14 @@ interface UIProps {
export const TxUI: FC<UIProps> = ({ state: txState, setState, resetState, estimateFee }) => {
const { accounts } = useSnapshot(state)
const { selectedAccount, selectedDestAccount, selectedTransaction, txFields, selectedFlags } =
txState
const {
selectedAccount,
selectedDestAccount,
selectedTransaction,
txFields,
selectedFlags,
hookParameters
} = txState
const accountOptions: SelectOption[] = accounts.map(acc => ({
label: acc.name,
@@ -110,7 +117,7 @@ export const TxUI: FC<UIProps> = ({ state: txState, setState, resetState, estima
[selectedTransaction?.value]
)
const richFields = ['TransactionType', 'Account']
const richFields = ['TransactionType', 'Account', 'HookParameters']
if (fields.Destination !== undefined) {
richFields.push('Destination')
}
@@ -120,7 +127,6 @@ export const TxUI: FC<UIProps> = ({ state: txState, setState, resetState, estima
}
const otherFields = Object.keys(txFields).filter(k => !richFields.includes(k)) as [keyof TxFields]
return (
<Container
css={{
@@ -130,94 +136,41 @@ export const TxUI: FC<UIProps> = ({ state: txState, setState, resetState, estima
}}
>
<Flex column fluid css={{ height: '100%', overflowY: 'auto', pr: '$1' }}>
<Flex
row
fluid
css={{
justifyContent: 'flex-end',
alignItems: 'center',
mb: '$3',
mt: '1px',
pr: '1px'
}}
>
<Text muted css={{ mr: '$3' }}>
Transaction type:{' '}
</Text>
<TxField label="Transaction type">
<Select
instanceId="transactionsType"
placeholder="Select transaction type"
options={transactionsOptions}
hideSelectedOptions
css={{ width: '70%' }}
value={selectedTransaction}
onChange={(tt: any) => handleChangeTxType(tt)}
/>
</Flex>
<Flex
row
fluid
css={{
justifyContent: 'flex-end',
alignItems: 'center',
mb: '$3',
pr: '1px'
}}
>
<Text muted css={{ mr: '$3' }}>
Account:{' '}
</Text>
</TxField>
<TxField label="Account">
<Select
instanceId="from-account"
placeholder="Select your account"
css={{ width: '70%' }}
options={accountOptions}
value={selectedAccount}
onChange={(acc: any) => handleSetAccount(acc)} // TODO make react-select have correct types for acc
/>
</Flex>
</TxField>
{richFields.includes('Destination') && (
<Flex
row
fluid
css={{
justifyContent: 'flex-end',
alignItems: 'center',
mb: '$3',
pr: '1px'
}}
>
<Text muted css={{ mr: '$3', textAlign: 'end' }}>
Destination account:{' '}
</Text>
<TxField label="Destination account">
<Select
instanceId="to-account"
placeholder="Select the destination account"
css={{ width: '70%' }}
options={destAccountOptions}
value={selectedDestAccount}
isClearable
onChange={(acc: any) => setState({ selectedDestAccount: acc })}
/>
</Flex>
</TxField>
)}
{richFields.includes('Flags') && (
<Flex
row
fluid
css={{
justifyContent: 'flex-end',
alignItems: 'center',
mb: '$3',
pr: '1px'
}}
>
<Text muted css={{ mr: '$3' }}>
Flags:{' '}
</Text>
<TxField label="Flags">
<Select
isClearable
css={{ width: '70%' }}
instanceId="flags"
placeholder="Select flags to apply"
menuPosition="fixed"
@@ -229,7 +182,7 @@ export const TxUI: FC<UIProps> = ({ state: txState, setState, resetState, estima
selectedFlags ? selectedFlags.length >= flagsOptions.length - 1 : false
}
/>
</Flex>
</TxField>
)}
{otherFields.map(field => {
let _value = txFields[field]
@@ -251,19 +204,7 @@ export const TxUI: FC<UIProps> = ({ state: txState, setState, resetState, estima
let rows = isJson ? (value?.match(/\n/gm)?.length || 0) + 1 : undefined
if (rows && rows > 5) rows = 5
return (
<Flex column key={field} css={{ mb: '$2', pr: '1px' }}>
<Flex
row
fluid
css={{
justifyContent: 'flex-end',
alignItems: 'center',
position: 'relative'
}}
>
<Text muted css={{ mr: '$3' }}>
{field + (isXrp ? ' (XRP)' : '')}:{' '}
</Text>
<TxField key={field} label={field + (isXrp ? ' (XRP)' : '')}>
{isJson ? (
<Textarea
rows={rows}
@@ -271,7 +212,6 @@ export const TxUI: FC<UIProps> = ({ state: txState, setState, resetState, estima
spellCheck={false}
onChange={switchToJson}
css={{
width: '70%',
flex: 'inherit',
resize: 'vertical'
}}
@@ -298,7 +238,6 @@ export const TxUI: FC<UIProps> = ({ state: txState, setState, resetState, estima
: undefined
}
css={{
width: '70%',
flex: 'inherit',
'-moz-appearance': 'textfield',
'&::-webkit-outer-spin-button': {
@@ -333,11 +272,94 @@ export const TxUI: FC<UIProps> = ({ state: txState, setState, resetState, estima
Suggest
</Button>
)}
</Flex>
</Flex>
</TxField>
)
})}
<TxField multiLine label="Hook parameters">
<Flex column fluid>
{Object.entries(hookParameters).map(([id, { label, value }]) => (
<Flex column key={id} css={{ mb: '$2' }}>
<Flex row>
<Input
placeholder="Parameter name"
value={label}
onChange={e => {
setState({
hookParameters: {
...hookParameters,
[id]: { label: e.target.value, value }
}
})
}}
/>
<Input
css={{ mx: '$2' }}
placeholder="Value"
value={value}
onChange={e => {
setState({
hookParameters: {
...hookParameters,
[id]: { label, value: e.target.value }
}
})
}}
/>
<Button
onClick={() => {
const { [id]: _, ...rest } = hookParameters
setState({ hookParameters: rest })
}}
variant="destroy"
>
<Trash weight="regular" size="16px" />
</Button>
</Flex>
</Flex>
))}
<Button
outline
fullWidth
type="button"
onClick={() => {
const id = Object.keys(hookParameters).length
setState({
hookParameters: { ...hookParameters, [id]: { label: '', value: '' } }
})
}}
>
<Plus size="16px" />
Add Hook Parameter
</Button>
</Flex>
</TxField>
</Flex>
</Container>
)
}
export const TxField: FC<{ label: string; children: ReactNode; multiLine?: boolean }> = ({
label,
children,
multiLine = false
}) => {
return (
<Flex
row
fluid
css={{
justifyContent: 'flex-end',
alignItems: multiLine ? 'flex-start' : 'center',
position: 'relative',
mb: '$3',
mt: '1px',
pr: '1px'
}}
>
<Text muted css={{ mr: '$3', mt: multiLine ? '$2' : 0 }}>
{label}:{' '}
</Text>
<Flex css={{ width: '70%', alignItems: 'center' }}>{children}</Flex>
</Flex>
)
}

View File

@@ -6,7 +6,7 @@ import calculateHookOn, { TTS } from '../../utils/hookOnCalculator'
import { Link } from '../../components'
import { ref } from 'valtio'
import estimateFee from '../../utils/estimateFee'
import { SetHookData } from '../../utils/setHook'
import { SetHookData, toHex } from '../../utils/setHook'
import ResultLink from '../../components/ResultLink'
import { xrplSend } from './xrpl-client'
@@ -18,13 +18,6 @@ export const sha256 = async (string: string) => {
return hashHex
}
function toHex(str: string) {
var result = ''
for (var i = 0; i < str.length; i++) {
result += str.charCodeAt(i).toString(16)
}
return result.toUpperCase()
}
function arrayBufferToHex(arrayBuffer?: ArrayBuffer | null) {
if (!arrayBuffer) {

View File

@@ -31,6 +31,10 @@ export const sendTransaction = async (
...opts
}
const { logPrefix = '' } = options || {}
state.transactionLogs.push({
type: 'log',
message: `${logPrefix}${JSON.stringify(tx, null, 2)}`
})
try {
const signedAccount = derive.familySeed(account.secret)
const { signedTransaction } = sign(tx, signedAccount)

View File

@@ -5,17 +5,23 @@ import state from '.'
import { showAlert } from '../state/actions/showAlert'
import { parseJSON } from '../utils/json'
import { extractFlags, getFlags } from './constants/flags'
import { fromHex } from '../utils/setHook'
export type SelectOption = {
value: string
label: string
}
export type HookParameters = {
[key: string]: SelectOption
}
export interface TransactionState {
selectedTransaction: SelectOption | null
selectedAccount: SelectOption | null
selectedDestAccount: SelectOption | null
selectedFlags: SelectOption[] | null
hookParameters: HookParameters
txIsLoading: boolean
txIsDisabled: boolean
txFields: TxFields
@@ -24,9 +30,11 @@ export interface TransactionState {
estimatedFee?: string
}
const commonFields = ['TransactionType', 'Account', 'Sequence', "HookParameters"] as const;
export type TxFields = Omit<
Partial<typeof transactionsData[0]>,
'Account' | 'Sequence' | 'TransactionType'
typeof commonFields[number]
>
export const defaultTransaction: TransactionState = {
@@ -34,6 +42,7 @@ export const defaultTransaction: TransactionState = {
selectedAccount: null,
selectedDestAccount: null,
selectedFlags: null,
hookParameters: {},
txIsLoading: false,
txIsDisabled: false,
txFields: {},
@@ -158,7 +167,7 @@ export const prepareState = (value: string, transactionType?: string) => {
return
}
const { Account, TransactionType, Destination, ...rest } = options
const { Account, TransactionType, Destination, HookParameters, ...rest } = options
let tx: Partial<TransactionState> = {}
const schema = getTxFields(transactionType)
@@ -188,6 +197,14 @@ export const prepareState = (value: string, transactionType?: string) => {
tx.selectedTransaction = null
}
if (HookParameters && HookParameters instanceof Array) {
tx.hookParameters = HookParameters.reduce<TransactionState["hookParameters"]>((acc, cur, idx) => {
const param = { label: fromHex(cur.HookParameter?.HookParameterName || ""), value: fromHex(cur.HookParameter?.HookParameterValue || "") }
acc[idx] = param;
return acc;
}, {})
}
if (schema.Destination !== undefined) {
const dest = state.accounts.find(acc => acc.address === Destination)
if (dest) {
@@ -246,12 +263,12 @@ export const getTxFields = (tt?: string) => {
if (!txFields) return {}
let _txFields = Object.keys(txFields)
.filter(key => !['TransactionType', 'Account', 'Sequence'].includes(key))
.filter(key => !commonFields.includes(key as any))
.reduce<TxFields>((tf, key) => ((tf[key as keyof TxFields] = (txFields as any)[key]), tf), {})
return _txFields
}
export { transactionsData }
export { transactionsData, commonFields }
export const transactionsOptions = transactionsData.map(tx => ({
value: tx.TransactionType,

View File

@@ -74,3 +74,19 @@ export const getInvokeOptions = (content?: string) => {
return invokeOptions
}
export function toHex(str: string) {
var result = ''
for (var i = 0; i < str.length; i++) {
result += str.charCodeAt(i).toString(16)
}
return result.toUpperCase()
}
export function fromHex(hex: string) {
var str = ''
for (var i = 0; i < hex.length; i += 2) {
str += String.fromCharCode(parseInt(hex.substring(i, i + 2), 16))
}
return str
}