300 lines
7.5 KiB
TypeScript
300 lines
7.5 KiB
TypeScript
import { proxy } from 'valtio'
|
|
import { deepEqual } from '../utils/object'
|
|
import transactionsData from '../content/transactions.json'
|
|
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'
|
|
import { typeIs } from '../utils/helpers'
|
|
|
|
export type SelectOption = {
|
|
value: string
|
|
label: string
|
|
}
|
|
|
|
export type HookParameters = {
|
|
[key: string]: SelectOption
|
|
}
|
|
|
|
export type Memos = {
|
|
[key: string]: {
|
|
type: string
|
|
format: string
|
|
data: string
|
|
}
|
|
}
|
|
|
|
export interface TransactionState {
|
|
selectedTransaction: SelectOption | null
|
|
selectedAccount: SelectOption | null
|
|
selectedFlags: SelectOption[] | null
|
|
hookParameters: HookParameters
|
|
memos: Memos
|
|
txIsLoading: boolean
|
|
txIsDisabled: boolean
|
|
txFields: TxFields
|
|
viewType: 'json' | 'ui'
|
|
editorValue?: string
|
|
editorIsSaved: boolean
|
|
estimatedFee?: string
|
|
}
|
|
|
|
const commonFields = ['TransactionType', 'Account', 'Sequence', "HookParameters"] as const;
|
|
|
|
export type TxFields = Omit<
|
|
Partial<typeof transactionsData[0]>,
|
|
typeof commonFields[number]
|
|
>
|
|
|
|
export const defaultTransaction: TransactionState = {
|
|
selectedTransaction: null,
|
|
selectedAccount: null,
|
|
selectedFlags: null,
|
|
hookParameters: {},
|
|
memos: {},
|
|
editorIsSaved: true,
|
|
txIsLoading: false,
|
|
txIsDisabled: false,
|
|
txFields: {},
|
|
viewType: 'ui'
|
|
}
|
|
|
|
export const transactionsState = proxy({
|
|
transactions: [
|
|
{
|
|
header: 'test1.json',
|
|
state: { ...defaultTransaction }
|
|
}
|
|
],
|
|
activeHeader: 'test1.json'
|
|
})
|
|
|
|
export const renameTxState = (oldName: string, nwName: string) => {
|
|
const tx = transactionsState.transactions.find(tx => tx.header === oldName)
|
|
|
|
if (!tx) throw Error(`No transaction state exists with given header name ${oldName}`)
|
|
|
|
tx.header = nwName
|
|
}
|
|
|
|
/**
|
|
* Simple transaction state changer
|
|
* @param header Unique key and tab name for the transaction tab
|
|
* @param partialTx partial transaction state, `undefined` deletes the transaction
|
|
*
|
|
*/
|
|
export const modifyTxState = (
|
|
header: string,
|
|
partialTx?: Partial<TransactionState>,
|
|
opts: { replaceState?: boolean } = {}
|
|
) => {
|
|
const tx = transactionsState.transactions.find(tx => tx.header === header)
|
|
|
|
if (partialTx === undefined) {
|
|
transactionsState.transactions = transactionsState.transactions.filter(
|
|
tx => tx.header !== header
|
|
)
|
|
return
|
|
}
|
|
|
|
if (!tx) {
|
|
const state = {
|
|
...defaultTransaction,
|
|
...partialTx
|
|
}
|
|
transactionsState.transactions.push({
|
|
header,
|
|
state
|
|
})
|
|
return state
|
|
}
|
|
|
|
if (opts.replaceState) {
|
|
const repTx: TransactionState = {
|
|
...defaultTransaction,
|
|
...partialTx
|
|
}
|
|
tx.state = repTx
|
|
return repTx
|
|
}
|
|
|
|
Object.keys(partialTx).forEach(k => {
|
|
// Typescript mess here, but is definitely safe!
|
|
const s = tx.state as any
|
|
const p = partialTx as any // ? Make copy
|
|
if (!deepEqual(s[k], p[k])) s[k] = p[k]
|
|
})
|
|
|
|
return tx.state
|
|
}
|
|
|
|
export const prepareTransaction = (data: any) => {
|
|
let options = { ...data }
|
|
|
|
Object.keys(options).forEach(field => {
|
|
let _value = options[field]
|
|
if (!typeIs(_value, 'object')) return
|
|
// amount.xrp
|
|
if (_value.$type === 'amount.xrp') {
|
|
if (_value.$value) {
|
|
options[field] = (+(_value as any).$value * 1000000 + '')
|
|
} else {
|
|
options[field] = ""
|
|
}
|
|
}
|
|
// amount.token
|
|
if (_value.$type === 'amount.token') {
|
|
if (typeIs(_value.$value, 'string')) {
|
|
options[field] = parseJSON(_value.$value)
|
|
} else if (typeIs(_value.$value, 'object')) {
|
|
options[field] = _value.$value
|
|
} else {
|
|
options[field] = undefined
|
|
}
|
|
}
|
|
// account
|
|
if (_value.$type === 'account') {
|
|
options[field] = _value.$value?.toString() || ""
|
|
}
|
|
// json
|
|
if (_value.$type === 'json') {
|
|
const val = _value.$value;
|
|
let res: any = val;
|
|
if (typeIs(val, ["object", "array"])) {
|
|
options[field] = res
|
|
} else if (typeIs(val, "string") && (res = parseJSON(val))) {
|
|
options[field] = res;
|
|
} else {
|
|
options[field] = res;
|
|
}
|
|
}
|
|
})
|
|
|
|
return options
|
|
}
|
|
|
|
export const prepareState = (value: string, transactionType?: string) => {
|
|
const options = parseJSON(value)
|
|
if (!options) {
|
|
showAlert('Error!', {
|
|
body: 'Cannot save editor with malformed transaction.'
|
|
})
|
|
return
|
|
}
|
|
|
|
const { Account, TransactionType, HookParameters, Memos, ...rest } = options
|
|
let tx: Partial<TransactionState> = {}
|
|
const schema = getTxFields(transactionType)
|
|
|
|
if (Account) {
|
|
const acc = state.accounts.find(acc => acc.address === Account)
|
|
if (acc) {
|
|
tx.selectedAccount = {
|
|
label: acc.name,
|
|
value: acc.address
|
|
}
|
|
} else {
|
|
tx.selectedAccount = {
|
|
label: Account,
|
|
value: Account
|
|
}
|
|
}
|
|
} else {
|
|
tx.selectedAccount = null
|
|
}
|
|
|
|
if (TransactionType) {
|
|
tx.selectedTransaction = {
|
|
label: TransactionType,
|
|
value: TransactionType
|
|
}
|
|
} else {
|
|
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 (Memos && Memos instanceof Array) {
|
|
tx.memos = Memos.reduce<TransactionState["memos"]>((acc, cur, idx) => {
|
|
const memo = { data: fromHex(cur.Memo?.MemoData || ""), type: fromHex(cur.Memo?.MemoType || ""), format: fromHex(cur.Memo?.MemoFormat || "") }
|
|
acc[idx] = memo;
|
|
return acc;
|
|
}, {})
|
|
}
|
|
|
|
|
|
if (getFlags(TransactionType) && rest.Flags) {
|
|
const flags = extractFlags(TransactionType, rest.Flags)
|
|
|
|
rest.Flags = undefined
|
|
tx.selectedFlags = flags
|
|
}
|
|
|
|
Object.keys(rest).forEach(field => {
|
|
const value = rest[field]
|
|
const schemaVal = schema[field as keyof TxFields]
|
|
|
|
const isAmount = schemaVal &&
|
|
typeIs(schemaVal, "object") &&
|
|
schemaVal.$type.startsWith('amount.');
|
|
const isAccount = schemaVal &&
|
|
typeIs(schemaVal, "object") &&
|
|
schemaVal.$type.startsWith("account");
|
|
|
|
if (isAmount && ["number", "string"].includes(typeof value)) {
|
|
rest[field] = {
|
|
$type: 'amount.xrp', // TODO narrow typed $type.
|
|
$value: +value / 1000000 // ! maybe use bigint?
|
|
}
|
|
} else if (isAmount && typeof value === 'object') {
|
|
rest[field] = {
|
|
$type: 'amount.token',
|
|
$value: value
|
|
}
|
|
} else if (isAccount) {
|
|
rest[field] = {
|
|
$type: "account",
|
|
$value: value?.toString() || ""
|
|
}
|
|
}
|
|
else if (typeof value === 'object') {
|
|
rest[field] = {
|
|
$type: 'json',
|
|
$value: value
|
|
}
|
|
}
|
|
})
|
|
|
|
tx.txFields = rest
|
|
tx.editorIsSaved = true;
|
|
|
|
return tx
|
|
}
|
|
|
|
export const getTxFields = (tt?: string) => {
|
|
const txFields: TxFields | undefined = transactionsData.find(tx => tx.TransactionType === tt)
|
|
|
|
if (!txFields) return {}
|
|
|
|
let _txFields = Object.keys(txFields)
|
|
.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, commonFields }
|
|
|
|
export const transactionsOptions = transactionsData.map(tx => ({
|
|
value: tx.TransactionType,
|
|
label: tx.TransactionType
|
|
}))
|
|
|
|
export const defaultTransactionType = transactionsOptions.find(tt => tt.value === 'Payment')
|