Add flags UI to Payment transaction.
This commit is contained in:
@@ -19,6 +19,7 @@ import { TxJson } from './json'
|
|||||||
import { TxUI } from './ui'
|
import { TxUI } from './ui'
|
||||||
import { default as _estimateFee } from '../../utils/estimateFee'
|
import { default as _estimateFee } from '../../utils/estimateFee'
|
||||||
import toast from 'react-hot-toast'
|
import toast from 'react-hot-toast'
|
||||||
|
import { combineFlags, extractFlags, transactionFlags } from '../../state/constants/flags'
|
||||||
|
|
||||||
export interface TransactionProps {
|
export interface TransactionProps {
|
||||||
header: string
|
header: string
|
||||||
@@ -39,14 +40,17 @@ const Transaction: FC<TransactionProps> = ({ header, state: txState, ...props })
|
|||||||
|
|
||||||
const prepareOptions = useCallback(
|
const prepareOptions = useCallback(
|
||||||
(state: Partial<TransactionState> = txState) => {
|
(state: Partial<TransactionState> = txState) => {
|
||||||
const { selectedTransaction, selectedDestAccount, selectedAccount, txFields } = state
|
const { selectedTransaction, selectedDestAccount, selectedAccount, txFields, selectedFlags } =
|
||||||
|
state
|
||||||
|
|
||||||
const TransactionType = selectedTransaction?.value || null
|
const TransactionType = selectedTransaction?.value || null
|
||||||
const Destination = selectedDestAccount?.value || txFields?.Destination
|
const Destination = selectedDestAccount?.value || txFields?.Destination
|
||||||
const Account = selectedAccount?.value || null
|
const Account = selectedAccount?.value || null
|
||||||
|
const Flags = combineFlags(selectedFlags?.map(flag => flag.value)) || txFields?.Flags
|
||||||
|
|
||||||
return prepareTransaction({
|
return prepareTransaction({
|
||||||
...txFields,
|
...txFields,
|
||||||
|
Flags,
|
||||||
TransactionType,
|
TransactionType,
|
||||||
Destination,
|
Destination,
|
||||||
Account
|
Account
|
||||||
@@ -136,8 +140,13 @@ const Transaction: FC<TransactionProps> = ({ header, state: txState, ...props })
|
|||||||
} else {
|
} else {
|
||||||
fields.Destination = undefined
|
fields.Destination = undefined
|
||||||
}
|
}
|
||||||
nwState.txFields = fields
|
|
||||||
|
|
||||||
|
if (transactionType?.value && transactionFlags[transactionType?.value] && fields.Flags) {
|
||||||
|
nwState.selectedFlags = extractFlags(transactionType.value, fields.Flags)
|
||||||
|
fields.Flags = undefined
|
||||||
|
}
|
||||||
|
|
||||||
|
nwState.txFields = fields
|
||||||
const state = modifyTxState(header, nwState, { replaceState: true })
|
const state = modifyTxState(header, nwState, { replaceState: true })
|
||||||
const editorValue = getJsonString(state)
|
const editorValue = getJsonString(state)
|
||||||
return setState({ editorValue })
|
return setState({ editorValue })
|
||||||
@@ -179,7 +188,12 @@ const Transaction: FC<TransactionProps> = ({ header, state: txState, ...props })
|
|||||||
estimateFee={estimateFee}
|
estimateFee={estimateFee}
|
||||||
/>
|
/>
|
||||||
) : (
|
) : (
|
||||||
<TxUI state={txState} setState={setState} estimateFee={estimateFee} />
|
<TxUI
|
||||||
|
state={txState}
|
||||||
|
resetState={resetState}
|
||||||
|
setState={setState}
|
||||||
|
estimateFee={estimateFee}
|
||||||
|
/>
|
||||||
)}
|
)}
|
||||||
<Flex
|
<Flex
|
||||||
row
|
row
|
||||||
|
|||||||
@@ -17,16 +17,19 @@ import state from '../../state'
|
|||||||
import { streamState } from '../DebugStream'
|
import { streamState } from '../DebugStream'
|
||||||
import { Button } from '..'
|
import { Button } from '..'
|
||||||
import Textarea from '../Textarea'
|
import Textarea from '../Textarea'
|
||||||
|
import { transactionFlags } from '../../state/constants/flags'
|
||||||
|
|
||||||
interface UIProps {
|
interface UIProps {
|
||||||
setState: (pTx?: Partial<TransactionState> | undefined) => TransactionState | undefined
|
setState: (pTx?: Partial<TransactionState> | undefined) => TransactionState | undefined
|
||||||
|
resetState: (tt?: SelectOption) => TransactionState | undefined
|
||||||
state: TransactionState
|
state: TransactionState
|
||||||
estimateFee?: (...arg: any) => Promise<string | undefined>
|
estimateFee?: (...arg: any) => Promise<string | undefined>
|
||||||
}
|
}
|
||||||
|
|
||||||
export const TxUI: FC<UIProps> = ({ state: txState, setState, estimateFee }) => {
|
export const TxUI: FC<UIProps> = ({ state: txState, setState, resetState, estimateFee }) => {
|
||||||
const { accounts } = useSnapshot(state)
|
const { accounts } = useSnapshot(state)
|
||||||
const { selectedAccount, selectedDestAccount, selectedTransaction, txFields } = txState
|
const { selectedAccount, selectedDestAccount, selectedTransaction, txFields, selectedFlags } =
|
||||||
|
txState
|
||||||
|
|
||||||
const accountOptions: SelectOption[] = accounts.map(acc => ({
|
const accountOptions: SelectOption[] = accounts.map(acc => ({
|
||||||
label: acc.name,
|
label: acc.name,
|
||||||
@@ -40,23 +43,15 @@ export const TxUI: FC<UIProps> = ({ state: txState, setState, estimateFee }) =>
|
|||||||
}))
|
}))
|
||||||
.filter(acc => acc.value !== selectedAccount?.value)
|
.filter(acc => acc.value !== selectedAccount?.value)
|
||||||
|
|
||||||
|
const flagsOptions: SelectOption[] = Object.entries(
|
||||||
|
transactionFlags[selectedTransaction?.value || ''] || {}
|
||||||
|
).map(([label, value]) => ({
|
||||||
|
label,
|
||||||
|
value
|
||||||
|
}))
|
||||||
|
|
||||||
const [feeLoading, setFeeLoading] = useState(false)
|
const [feeLoading, setFeeLoading] = useState(false)
|
||||||
|
|
||||||
const resetFields = useCallback(
|
|
||||||
(tt: string) => {
|
|
||||||
const fields = getTxFields(tt)
|
|
||||||
|
|
||||||
if (fields.Destination !== undefined) {
|
|
||||||
setState({ selectedDestAccount: null })
|
|
||||||
fields.Destination = ''
|
|
||||||
} else {
|
|
||||||
fields.Destination = undefined
|
|
||||||
}
|
|
||||||
return setState({ txFields: fields })
|
|
||||||
},
|
|
||||||
[setState]
|
|
||||||
)
|
|
||||||
|
|
||||||
const handleSetAccount = (acc: SelectOption) => {
|
const handleSetAccount = (acc: SelectOption) => {
|
||||||
setState({ selectedAccount: acc })
|
setState({ selectedAccount: acc })
|
||||||
streamState.selectedAccount = acc
|
streamState.selectedAccount = acc
|
||||||
@@ -92,11 +87,11 @@ export const TxUI: FC<UIProps> = ({ state: txState, setState, estimateFee }) =>
|
|||||||
(tt: SelectOption) => {
|
(tt: SelectOption) => {
|
||||||
setState({ selectedTransaction: tt })
|
setState({ selectedTransaction: tt })
|
||||||
|
|
||||||
const newState = resetFields(tt.value)
|
const newState = resetState(tt)
|
||||||
|
|
||||||
handleEstimateFee(newState, true)
|
handleEstimateFee(newState, true)
|
||||||
},
|
},
|
||||||
[handleEstimateFee, resetFields, setState]
|
[handleEstimateFee, resetState, setState]
|
||||||
)
|
)
|
||||||
|
|
||||||
const switchToJson = () => setState({ viewType: 'json' })
|
const switchToJson = () => setState({ viewType: 'json' })
|
||||||
@@ -115,12 +110,16 @@ export const TxUI: FC<UIProps> = ({ state: txState, setState, estimateFee }) =>
|
|||||||
[selectedTransaction?.value]
|
[selectedTransaction?.value]
|
||||||
)
|
)
|
||||||
|
|
||||||
const specialFields = ['TransactionType', 'Account']
|
const richFields = ['TransactionType', 'Account']
|
||||||
if (fields.Destination !== undefined) {
|
if (fields.Destination !== undefined) {
|
||||||
specialFields.push('Destination')
|
richFields.push('Destination')
|
||||||
}
|
}
|
||||||
|
|
||||||
const otherFields = Object.keys(txFields).filter(k => !specialFields.includes(k)) as [
|
if (flagsOptions.length) {
|
||||||
|
richFields.push('Flags')
|
||||||
|
}
|
||||||
|
|
||||||
|
const otherFields = Object.keys(txFields).filter(k => !richFields.includes(k)) as [
|
||||||
keyof TxFields
|
keyof TxFields
|
||||||
]
|
]
|
||||||
|
|
||||||
@@ -179,7 +178,7 @@ export const TxUI: FC<UIProps> = ({ state: txState, setState, estimateFee }) =>
|
|||||||
onChange={(acc: any) => handleSetAccount(acc)} // TODO make react-select have correct types for acc
|
onChange={(acc: any) => handleSetAccount(acc)} // TODO make react-select have correct types for acc
|
||||||
/>
|
/>
|
||||||
</Flex>
|
</Flex>
|
||||||
{fields.Destination !== undefined && (
|
{richFields.includes('Destination') && (
|
||||||
<Flex
|
<Flex
|
||||||
row
|
row
|
||||||
fluid
|
fluid
|
||||||
@@ -204,6 +203,36 @@ export const TxUI: FC<UIProps> = ({ state: txState, setState, estimateFee }) =>
|
|||||||
/>
|
/>
|
||||||
</Flex>
|
</Flex>
|
||||||
)}
|
)}
|
||||||
|
{richFields.includes('Flags') && (
|
||||||
|
<Flex
|
||||||
|
row
|
||||||
|
fluid
|
||||||
|
css={{
|
||||||
|
justifyContent: 'flex-end',
|
||||||
|
alignItems: 'center',
|
||||||
|
mb: '$3',
|
||||||
|
pr: '1px'
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Text muted css={{ mr: '$3' }}>
|
||||||
|
Flags:{' '}
|
||||||
|
</Text>
|
||||||
|
<Select
|
||||||
|
isClearable
|
||||||
|
css={{ width: '70%' }}
|
||||||
|
instanceId="flags"
|
||||||
|
placeholder="Select flags to apply"
|
||||||
|
menuPosition="fixed"
|
||||||
|
value={selectedFlags}
|
||||||
|
isMulti
|
||||||
|
options={flagsOptions}
|
||||||
|
onChange={flags => setState({ selectedFlags: flags as any })}
|
||||||
|
closeMenuOnSelect={
|
||||||
|
selectedFlags ? selectedFlags.length >= flagsOptions.length - 1 : false
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
</Flex>
|
||||||
|
)}
|
||||||
{otherFields.map(field => {
|
{otherFields.map(field => {
|
||||||
let _value = txFields[field]
|
let _value = txFields[field]
|
||||||
|
|
||||||
|
|||||||
@@ -6,7 +6,7 @@
|
|||||||
"DestinationTag": 13,
|
"DestinationTag": 13,
|
||||||
"Fee": "2000000",
|
"Fee": "2000000",
|
||||||
"Sequence": 2470665,
|
"Sequence": 2470665,
|
||||||
"Flags": 2147483648
|
"Flags": "2147483648"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"TransactionType": "AccountSet",
|
"TransactionType": "AccountSet",
|
||||||
@@ -48,7 +48,7 @@
|
|||||||
"Account": "rsUiUMpnrgxQp24dJYZDhmV4bE3aBtQyt8",
|
"Account": "rsUiUMpnrgxQp24dJYZDhmV4bE3aBtQyt8",
|
||||||
"Authorize": "rEhxGqkqPPSxQ3P25J66ft5TwpzV14k2de",
|
"Authorize": "rEhxGqkqPPSxQ3P25J66ft5TwpzV14k2de",
|
||||||
"Fee": "10",
|
"Fee": "10",
|
||||||
"Flags": 2147483648,
|
"Flags": "2147483648",
|
||||||
"Sequence": 2
|
"Sequence": 2
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -109,7 +109,9 @@
|
|||||||
"Fee": "10",
|
"Fee": "10",
|
||||||
"NFTokenOffers": {
|
"NFTokenOffers": {
|
||||||
"$type": "json",
|
"$type": "json",
|
||||||
"$value": ["4AAAEEA76E3C8148473CB3840CE637676E561FB02BD4CA22CA59729EA815B862"]
|
"$value": [
|
||||||
|
"4AAAEEA76E3C8148473CB3840CE637676E561FB02BD4CA22CA59729EA815B862"
|
||||||
|
]
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -120,7 +122,7 @@
|
|||||||
"$value": "100",
|
"$value": "100",
|
||||||
"$type": "xrp"
|
"$type": "xrp"
|
||||||
},
|
},
|
||||||
"Flags": 1,
|
"Flags": "1",
|
||||||
"Destination": "",
|
"Destination": "",
|
||||||
"Fee": "10"
|
"Fee": "10"
|
||||||
},
|
},
|
||||||
@@ -128,7 +130,7 @@
|
|||||||
"TransactionType": "OfferCancel",
|
"TransactionType": "OfferCancel",
|
||||||
"Account": "ra5nK24KXen9AHvsdFTKHSANinZseWnPcX",
|
"Account": "ra5nK24KXen9AHvsdFTKHSANinZseWnPcX",
|
||||||
"Fee": "12",
|
"Fee": "12",
|
||||||
"Flags": 0,
|
"Flags": "0",
|
||||||
"LastLedgerSequence": 7108629,
|
"LastLedgerSequence": 7108629,
|
||||||
"OfferSequence": 6,
|
"OfferSequence": 6,
|
||||||
"Sequence": 7
|
"Sequence": 7
|
||||||
@@ -137,7 +139,7 @@
|
|||||||
"TransactionType": "OfferCreate",
|
"TransactionType": "OfferCreate",
|
||||||
"Account": "ra5nK24KXen9AHvsdFTKHSANinZseWnPcX",
|
"Account": "ra5nK24KXen9AHvsdFTKHSANinZseWnPcX",
|
||||||
"Fee": "12",
|
"Fee": "12",
|
||||||
"Flags": 0,
|
"Flags": "0",
|
||||||
"LastLedgerSequence": 7108682,
|
"LastLedgerSequence": 7108682,
|
||||||
"Sequence": 8,
|
"Sequence": 8,
|
||||||
"TakerGets": "6000000",
|
"TakerGets": "6000000",
|
||||||
@@ -155,7 +157,7 @@
|
|||||||
"$type": "xrp"
|
"$type": "xrp"
|
||||||
},
|
},
|
||||||
"Fee": "12",
|
"Fee": "12",
|
||||||
"Flags": 2147483648,
|
"Flags": "2147483648",
|
||||||
"Sequence": 2
|
"Sequence": 2
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -185,14 +187,14 @@
|
|||||||
"Fee": "10"
|
"Fee": "10"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"Flags": 0,
|
"Flags": "0",
|
||||||
"TransactionType": "SetRegularKey",
|
"TransactionType": "SetRegularKey",
|
||||||
"Account": "rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn",
|
"Account": "rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn",
|
||||||
"Fee": "12",
|
"Fee": "12",
|
||||||
"RegularKey": "rAR8rR8sUkBoCZFawhkWzY4Y5YoyuznwD"
|
"RegularKey": "rAR8rR8sUkBoCZFawhkWzY4Y5YoyuznwD"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"Flags": 0,
|
"Flags": "0",
|
||||||
"TransactionType": "SignerListSet",
|
"TransactionType": "SignerListSet",
|
||||||
"Account": "rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn",
|
"Account": "rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn",
|
||||||
"Fee": "12",
|
"Fee": "12",
|
||||||
@@ -232,7 +234,7 @@
|
|||||||
"TransactionType": "TrustSet",
|
"TransactionType": "TrustSet",
|
||||||
"Account": "ra5nK24KXen9AHvsdFTKHSANinZseWnPcX",
|
"Account": "ra5nK24KXen9AHvsdFTKHSANinZseWnPcX",
|
||||||
"Fee": "12",
|
"Fee": "12",
|
||||||
"Flags": 262144,
|
"Flags": "262144",
|
||||||
"LastLedgerSequence": 8007750,
|
"LastLedgerSequence": 8007750,
|
||||||
"LimitAmount": {
|
"LimitAmount": {
|
||||||
"$type": "json",
|
"$type": "json",
|
||||||
|
|||||||
33
state/constants/flags.ts
Normal file
33
state/constants/flags.ts
Normal file
@@ -0,0 +1,33 @@
|
|||||||
|
import { SelectOption } from '../transactions';
|
||||||
|
|
||||||
|
interface Flags {
|
||||||
|
[key: string]: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const transactionFlags: { [key: /* TransactionType */ string]: Flags } = {
|
||||||
|
Payment: {
|
||||||
|
tfNoDirectRipple: "0x00010000",
|
||||||
|
tfPartialPayment: "0x00020000",
|
||||||
|
tfLimitQuality: "0x00040000",
|
||||||
|
},
|
||||||
|
// TODO Add more here
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
export function combineFlags(flags?: string[]): string | undefined {
|
||||||
|
if (!flags) return
|
||||||
|
|
||||||
|
const num = flags.reduce((cumm, curr) => cumm | BigInt(curr), BigInt(0))
|
||||||
|
return num.toString()
|
||||||
|
}
|
||||||
|
|
||||||
|
export function extractFlags(transactionType: string, flags?: string | number,): SelectOption[] {
|
||||||
|
const flagsObj = transactionFlags[transactionType]
|
||||||
|
if (!flags || !flagsObj) return []
|
||||||
|
|
||||||
|
const extracted = Object.entries(flagsObj).reduce((cumm, [label, value]) => {
|
||||||
|
return (BigInt(flags) & BigInt(value)) ? cumm.concat({ label, value }) : cumm
|
||||||
|
}, [] as SelectOption[])
|
||||||
|
|
||||||
|
return extracted
|
||||||
|
}
|
||||||
@@ -4,6 +4,7 @@ import transactionsData from '../content/transactions.json'
|
|||||||
import state from '.'
|
import state from '.'
|
||||||
import { showAlert } from '../state/actions/showAlert'
|
import { showAlert } from '../state/actions/showAlert'
|
||||||
import { parseJSON } from '../utils/json'
|
import { parseJSON } from '../utils/json'
|
||||||
|
import { extractFlags, transactionFlags } from './constants/flags'
|
||||||
|
|
||||||
export type SelectOption = {
|
export type SelectOption = {
|
||||||
value: string
|
value: string
|
||||||
@@ -14,6 +15,7 @@ export interface TransactionState {
|
|||||||
selectedTransaction: SelectOption | null
|
selectedTransaction: SelectOption | null
|
||||||
selectedAccount: SelectOption | null
|
selectedAccount: SelectOption | null
|
||||||
selectedDestAccount: SelectOption | null
|
selectedDestAccount: SelectOption | null
|
||||||
|
selectedFlags: SelectOption[] | null
|
||||||
txIsLoading: boolean
|
txIsLoading: boolean
|
||||||
txIsDisabled: boolean
|
txIsDisabled: boolean
|
||||||
txFields: TxFields
|
txFields: TxFields
|
||||||
@@ -31,6 +33,7 @@ export const defaultTransaction: TransactionState = {
|
|||||||
selectedTransaction: null,
|
selectedTransaction: null,
|
||||||
selectedAccount: null,
|
selectedAccount: null,
|
||||||
selectedDestAccount: null,
|
selectedDestAccount: null,
|
||||||
|
selectedFlags: null,
|
||||||
txIsLoading: false,
|
txIsLoading: false,
|
||||||
txIsDisabled: false,
|
txIsDisabled: false,
|
||||||
txFields: {},
|
txFields: {},
|
||||||
@@ -128,9 +131,8 @@ export const prepareTransaction = (data: any) => {
|
|||||||
try {
|
try {
|
||||||
options[field] = JSON.parse(_value.$value)
|
options[field] = JSON.parse(_value.$value)
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
const message = `Input error for json field '${field}': ${
|
const message = `Input error for json field '${field}': ${error instanceof Error ? error.message : ''
|
||||||
error instanceof Error ? error.message : ''
|
}`
|
||||||
}`
|
|
||||||
console.error(message)
|
console.error(message)
|
||||||
options[field] = _value.$value
|
options[field] = _value.$value
|
||||||
}
|
}
|
||||||
@@ -205,6 +207,13 @@ export const prepareState = (value: string, transactionType?: string) => {
|
|||||||
rest.Destination = Destination
|
rest.Destination = Destination
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (transactionFlags[TransactionType] && rest.Flags) {
|
||||||
|
const flags = extractFlags(TransactionType, rest.Flags)
|
||||||
|
|
||||||
|
rest.Flags = undefined
|
||||||
|
tx.selectedFlags = flags
|
||||||
|
}
|
||||||
|
|
||||||
Object.keys(rest).forEach(field => {
|
Object.keys(rest).forEach(field => {
|
||||||
const value = rest[field]
|
const value = rest[field]
|
||||||
const schemaVal = schema[field as keyof TxFields]
|
const schemaVal = schema[field as keyof TxFields]
|
||||||
|
|||||||
Reference in New Issue
Block a user