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' export type SelectOption = { value: string label: string } export interface TransactionState { selectedTransaction: SelectOption | null selectedAccount: SelectOption | null selectedDestAccount: SelectOption | null txIsLoading: boolean txIsDisabled: boolean txFields: TxFields viewType: 'json' | 'ui' editorValue?: string estimatedFee?: string } export type TxFields = Omit< Partial, 'Account' | 'Sequence' | 'TransactionType' > export const defaultTransaction: TransactionState = { selectedTransaction: null, selectedAccount: null, selectedDestAccount: null, 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, 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 } // state to tx options export const prepareTransaction = (data: any) => { let options = { ...data } Object.keys(options).forEach(field => { let _value = options[field] // convert xrp if (_value && typeof _value === 'object' && _value.$type === 'xrp') { if (+_value.$value) { options[field] = (+_value.$value * 1000000 + '') as any } else { options[field] = undefined // 👇 💀 } } // handle type: `json` if (_value && typeof _value === 'object' && _value.$type === 'json') { if (typeof _value.$value === 'object') { options[field] = _value.$value } else { try { options[field] = JSON.parse(_value.$value) } catch (error) { const message = `Input error for json field '${field}': ${ error instanceof Error ? error.message : '' }` console.error(message) options[field] = _value.$value } } } // delete unnecessary fields if (!options[field]) { delete options[field] } }) return options } // editor value to state 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, Destination, ...rest } = options let tx: Partial = {} 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 (schema.Destination !== undefined) { const dest = state.accounts.find(acc => acc.address === Destination) if (dest) { tx.selectedDestAccount = { label: dest.name, value: dest.address } } else if (Destination) { tx.selectedDestAccount = { label: Destination, value: Destination } } else { tx.selectedDestAccount = null } } else if (Destination) { rest.Destination = Destination } Object.keys(rest).forEach(field => { const value = rest[field] const schemaVal = schema[field as keyof TxFields] const isXrp = typeof value !== 'object' && schemaVal && typeof schemaVal === 'object' && schemaVal.$type === 'xrp' if (isXrp) { rest[field] = { $type: 'xrp', $value: +value / 1000000 // ! maybe use bigint? } } else if (typeof value === 'object') { rest[field] = { $type: 'json', $value: value } } }) tx.txFields = rest 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 => !['TransactionType', 'Account', 'Sequence'].includes(key)) .reduce((tf, key) => ((tf[key as keyof TxFields] = (txFields as any)[key]), tf), {}) return _txFields } export { transactionsData } export const transactionsOptions = transactionsData.map(tx => ({ value: tx.TransactionType, label: tx.TransactionType })) export const defaultTransactionType = transactionsOptions.find(tt => tt.value === 'Payment')