Files
xrpl-hooks-ide/state/transactions.ts

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')