Files
xrpl-hooks-ide/state/transactions.ts
2023-03-08 20:33:23 +05:30

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'
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
selectedDestAccount: 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,
selectedDestAccount: 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
}
// 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, 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 (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
}
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 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
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')