Compare commits
27 Commits
staging
...
fix/nft-cr
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
3340857575 | ||
|
|
e1f34c4beb | ||
|
|
54a89c969e | ||
|
|
ded867d997 | ||
|
|
0fce9af77c | ||
|
|
55c68c580a | ||
|
|
832a7997d1 | ||
|
|
4528e5a16e | ||
|
|
38f064c6d8 | ||
|
|
fbf4565dbc | ||
|
|
9001c64fed | ||
|
|
03b768db4e | ||
|
|
825af0db89 | ||
|
|
31043f33ab | ||
|
|
39699a1cb9 | ||
|
|
b50b300307 | ||
|
|
82c06cbb12 | ||
|
|
423ee18e6a | ||
|
|
3bb26d0c9b | ||
|
|
43c83d0de6 | ||
|
|
6bb407cb0f | ||
|
|
d7b29ba809 | ||
|
|
ca81d8ad41 | ||
|
|
a7e59d7b73 | ||
|
|
8f6b28cef5 | ||
|
|
c1815f272b | ||
|
|
76871a8041 |
@@ -5,7 +5,8 @@ GITHUB_ID=""
|
||||
NEXT_PUBLIC_COMPILE_API_ENDPOINT="http://localhost:9000/api/build"
|
||||
NEXT_PUBLIC_COMPILE_API_BASE_URL="http://localhost:9000"
|
||||
NEXT_PUBLIC_LANGUAGE_SERVER_API_ENDPOINT="ws://localhost:9000/language-server/c"
|
||||
NEXT_PUBLIC_TESTNET_URL="hooks-testnet-v2.xrpl-labs.com"
|
||||
NEXT_PUBLIC_DEBUG_STREAM_URL="hooks-testnet-v2-debugstream.xrpl-labs.com"
|
||||
NEXT_PUBLIC_EXPLORER_URL="hooks-testnet-v2-explorer.xrpl-labs.com"
|
||||
NEXT_PUBLIC_SITE_URL=http://localhost:3000
|
||||
NEXT_PUBLIC_TESTNET_URL="hooks-testnet-v3.xrpl-labs.com"
|
||||
NEXT_PUBLIC_DEBUG_STREAM_URL="hooks-testnet-v3-debugstream.xrpl-labs.com"
|
||||
NEXT_PUBLIC_EXPLORER_URL="hooks-testnet-v3-explorer.xrpl-labs.com"
|
||||
NEXT_PUBLIC_NETWORK_ID="21338"
|
||||
NEXT_PUBLIC_SITE_URL="http://localhost:3000"
|
||||
4
.gitignore
vendored
4
.gitignore
vendored
@@ -33,3 +33,7 @@ yarn-error.log*
|
||||
# vercel
|
||||
.vercel
|
||||
.vscode
|
||||
|
||||
# yarn
|
||||
.yarnrc.yml
|
||||
.yarn/
|
||||
|
||||
@@ -33,6 +33,7 @@ import { addFunds } from '../state/actions/addFaucetAccount'
|
||||
import { deleteHook } from '../state/actions/deployHook'
|
||||
import { capitalize } from '../utils/helpers'
|
||||
import { deleteAccount } from '../state/actions/deleteAccount'
|
||||
import { xrplSend } from '../state/actions/xrpl-client'
|
||||
|
||||
export const AccountDialog = ({
|
||||
activeAccountAddress,
|
||||
@@ -301,7 +302,7 @@ const Accounts: FC<AccountProps> = props => {
|
||||
const fetchAccInfo = async () => {
|
||||
if (snap.clientStatus === 'online') {
|
||||
const requests = snap.accounts.map(acc =>
|
||||
snap.client?.send({
|
||||
xrplSend({
|
||||
id: `hooks-builder-req-info-${acc.address}`,
|
||||
command: 'account_info',
|
||||
account: acc.address
|
||||
@@ -329,7 +330,7 @@ const Accounts: FC<AccountProps> = props => {
|
||||
}
|
||||
})
|
||||
const objectRequests = snap.accounts.map(acc => {
|
||||
return snap.client?.send({
|
||||
return xrplSend({
|
||||
id: `hooks-builder-req-objects-${acc.address}`,
|
||||
command: 'account_objects',
|
||||
account: acc.address
|
||||
|
||||
@@ -15,7 +15,7 @@ const contentShow = keyframes({
|
||||
'100%': { opacity: 1 }
|
||||
})
|
||||
const StyledOverlay = styled(DialogPrimitive.Overlay, {
|
||||
zIndex: 10000,
|
||||
zIndex: 3000,
|
||||
backgroundColor: blackA.blackA9,
|
||||
position: 'fixed',
|
||||
inset: 0,
|
||||
|
||||
@@ -21,7 +21,7 @@ import { saveFile } from '../../state/actions/saveFile'
|
||||
import { getErrors, getTags } from '../../utils/comment-parser'
|
||||
import toast from 'react-hot-toast'
|
||||
|
||||
const generateHtmlTemplate = (code: string, data?: Record<string, any>) => {
|
||||
const generateHtmlTemplate = async (code: string, data?: Record<string, any>) => {
|
||||
let processString: string | undefined
|
||||
const process = { env: { NODE_ENV: 'production' } } as any
|
||||
if (data) {
|
||||
@@ -29,8 +29,10 @@ const generateHtmlTemplate = (code: string, data?: Record<string, any>) => {
|
||||
process.env[key] = data[key]
|
||||
})
|
||||
}
|
||||
|
||||
processString = JSON.stringify(process)
|
||||
|
||||
const libs = (await import("xrpl-accountlib/dist/browser.hook-bundle.js")).default;
|
||||
return `
|
||||
<html>
|
||||
<head>
|
||||
@@ -72,7 +74,9 @@ const generateHtmlTemplate = (code: string, data?: Record<string, any>) => {
|
||||
|
||||
window.addEventListener('error', windowErrorHandler);
|
||||
</script>
|
||||
|
||||
<script>
|
||||
${libs}
|
||||
</script>
|
||||
<script type="module">
|
||||
${code}
|
||||
</script>
|
||||
@@ -100,6 +104,7 @@ const RunScript: React.FC<{ file: IFile }> = ({ file: { content, name } }) => {
|
||||
const [fields, setFields] = useState<Fields>({})
|
||||
const [iFrameCode, setIframeCode] = useState('')
|
||||
const [isDialogOpen, setIsDialogOpen] = useState(false)
|
||||
const [isLoading, setIsLoading] = useState(false)
|
||||
|
||||
const getFields = useCallback(() => {
|
||||
const inputTags = ['input', 'param', 'arg', 'argument']
|
||||
@@ -127,16 +132,31 @@ const RunScript: React.FC<{ file: IFile }> = ({ file: { content, name } }) => {
|
||||
return fields
|
||||
}, [content])
|
||||
|
||||
const runScript = useCallback(() => {
|
||||
const runScript = useCallback(async () => {
|
||||
setIsLoading(true);
|
||||
try {
|
||||
// Show loading toast only after 1 second, otherwise skip it.
|
||||
let loaded = false
|
||||
let toastId: string | undefined;
|
||||
setTimeout(() => {
|
||||
if (!loaded) {
|
||||
toastId = toast.loading('Loading packages, may take a few seconds...', {
|
||||
position: 'bottom-center',
|
||||
})
|
||||
}
|
||||
}, 1000)
|
||||
|
||||
let data: any = {}
|
||||
Object.keys(fields).forEach(key => {
|
||||
data[key] = fields[key].value
|
||||
})
|
||||
const template = generateHtmlTemplate(content, data)
|
||||
|
||||
const template = await generateHtmlTemplate(content, data)
|
||||
setIframeCode(template)
|
||||
|
||||
loaded = true
|
||||
if (toastId) {
|
||||
toast.dismiss(toastId)
|
||||
}
|
||||
state.scriptLogs = [{ type: 'success', message: 'Started running...' }]
|
||||
} catch (err) {
|
||||
state.scriptLogs = [
|
||||
@@ -145,6 +165,7 @@ const RunScript: React.FC<{ file: IFile }> = ({ file: { content, name } }) => {
|
||||
{ type: 'error', message: err?.message || 'Could not parse template' }
|
||||
]
|
||||
}
|
||||
setIsLoading(false);
|
||||
}, [content, fields, snap.scriptLogs])
|
||||
|
||||
useEffect(() => {
|
||||
@@ -174,11 +195,11 @@ const RunScript: React.FC<{ file: IFile }> = ({ file: { content, name } }) => {
|
||||
|
||||
const isDisabled = Object.values(fields).some(field => field.required && !field.value)
|
||||
|
||||
const handleRun = useCallback(() => {
|
||||
const handleRun = useCallback(async () => {
|
||||
if (isDisabled) return toast.error('Please fill in all the required fields.')
|
||||
|
||||
state.scriptLogs = []
|
||||
runScript()
|
||||
await runScript();
|
||||
setIsDialogOpen(false)
|
||||
}, [isDisabled, runScript])
|
||||
|
||||
@@ -279,7 +300,7 @@ const RunScript: React.FC<{ file: IFile }> = ({ file: { content, name } }) => {
|
||||
<DialogClose asChild>
|
||||
<Button outline>Cancel</Button>
|
||||
</DialogClose>
|
||||
<Button variant="primary" isDisabled={isDisabled} onClick={handleRun}>
|
||||
<Button variant="primary" isDisabled={isDisabled || isLoading} isLoading={isLoading} onClick={handleRun}>
|
||||
Run script
|
||||
</Button>
|
||||
</Flex>
|
||||
|
||||
@@ -38,7 +38,8 @@ const Select = forwardRef<any, Props>((props, ref) => {
|
||||
container: provided => {
|
||||
return {
|
||||
...provided,
|
||||
position: 'relative'
|
||||
position: 'relative',
|
||||
width: '100%'
|
||||
}
|
||||
},
|
||||
singleValue: provided => ({
|
||||
|
||||
71
components/Sequence.tsx
Normal file
71
components/Sequence.tsx
Normal file
@@ -0,0 +1,71 @@
|
||||
import { FC, useCallback, useState } from 'react'
|
||||
import state from '../state'
|
||||
import { Flex, Input, Button } from '.'
|
||||
import fetchAccountInfo from '../utils/accountInfo'
|
||||
import { useSnapshot } from 'valtio'
|
||||
|
||||
interface AccountSequenceProps {
|
||||
address?: string
|
||||
}
|
||||
const AccountSequence: FC<AccountSequenceProps> = ({ address }) => {
|
||||
const { accounts } = useSnapshot(state)
|
||||
const account = accounts.find(acc => acc.address === address)
|
||||
const [isLoading, setIsLoading] = useState(false)
|
||||
const setSequence = useCallback(
|
||||
(sequence: number) => {
|
||||
const acc = state.accounts.find(acc => acc.address == address)
|
||||
if (!acc) return
|
||||
acc.sequence = sequence
|
||||
},
|
||||
[address]
|
||||
)
|
||||
const handleUpdateSequence = useCallback(
|
||||
async (silent?: boolean) => {
|
||||
if (!account) return
|
||||
setIsLoading(true)
|
||||
|
||||
const info = await fetchAccountInfo(account.address, { silent })
|
||||
if (info) {
|
||||
setSequence(info.Sequence)
|
||||
}
|
||||
|
||||
setIsLoading(false)
|
||||
},
|
||||
[account, setSequence]
|
||||
)
|
||||
const disabled = !account
|
||||
return (
|
||||
<Flex row align="center" fluid>
|
||||
<Input
|
||||
placeholder="Account sequence"
|
||||
value={account?.sequence || ""}
|
||||
disabled={!account}
|
||||
type="number"
|
||||
readOnly={true}
|
||||
/>
|
||||
<Button
|
||||
size="xs"
|
||||
variant="primary"
|
||||
type="button"
|
||||
outline
|
||||
disabled={disabled}
|
||||
isDisabled={disabled}
|
||||
isLoading={isLoading}
|
||||
css={{
|
||||
background: '$backgroundAlt',
|
||||
position: 'absolute',
|
||||
right: '$2',
|
||||
fontSize: '$xs',
|
||||
cursor: 'pointer',
|
||||
alignContent: 'center',
|
||||
display: 'flex'
|
||||
}}
|
||||
onClick={() => handleUpdateSequence()}
|
||||
>
|
||||
Update
|
||||
</Button>
|
||||
</Flex>
|
||||
)
|
||||
}
|
||||
|
||||
export default AccountSequence
|
||||
@@ -21,6 +21,7 @@ import { prepareDeployHookTx, sha256 } from '../state/actions/deployHook'
|
||||
import estimateFee from '../utils/estimateFee'
|
||||
import { getParameters, getInvokeOptions, transactionOptions, SetHookData } from '../utils/setHook'
|
||||
import { capitalize } from '../utils/helpers'
|
||||
import AccountSequence from './Sequence'
|
||||
|
||||
export const SetHookDialog: React.FC<{ accountAddress: string }> = React.memo(
|
||||
({ accountAddress }) => {
|
||||
@@ -190,6 +191,10 @@ export const SetHookDialog: React.FC<{ accountAddress: string }> = React.memo(
|
||||
onChange={(acc: any) => setSelectedAccount(acc)}
|
||||
/>
|
||||
</Box>
|
||||
<Box css={{ width: '100%', position: 'relative' }}>
|
||||
<Label>Sequence</Label>
|
||||
<AccountSequence address={selectedAccount?.value} />
|
||||
</Box>
|
||||
<Box css={{ width: '100%' }}>
|
||||
<Label>Invoke on transactions</Label>
|
||||
<Controller
|
||||
|
||||
@@ -20,6 +20,7 @@ import { TxUI } from './ui'
|
||||
import { default as _estimateFee } from '../../utils/estimateFee'
|
||||
import toast from 'react-hot-toast'
|
||||
import { combineFlags, extractFlags, transactionFlags } from '../../state/constants/flags'
|
||||
import { SetHookData, toHex } from '../../utils/setHook'
|
||||
|
||||
export interface TransactionProps {
|
||||
header: string
|
||||
@@ -40,20 +41,43 @@ const Transaction: FC<TransactionProps> = ({ header, state: txState, ...props })
|
||||
|
||||
const prepareOptions = useCallback(
|
||||
(state: Partial<TransactionState> = txState) => {
|
||||
const { selectedTransaction, selectedDestAccount, selectedAccount, txFields, selectedFlags } =
|
||||
state
|
||||
const {
|
||||
selectedTransaction,
|
||||
selectedDestAccount,
|
||||
selectedAccount,
|
||||
txFields,
|
||||
selectedFlags,
|
||||
hookParameters,
|
||||
memos
|
||||
} = state
|
||||
|
||||
const TransactionType = selectedTransaction?.value || null
|
||||
const Destination = selectedDestAccount?.value || txFields?.Destination
|
||||
const Account = selectedAccount?.value || null
|
||||
const Flags = combineFlags(selectedFlags?.map(flag => flag.value)) || txFields?.Flags
|
||||
const HookParameters = Object.entries(hookParameters || {}).reduce<
|
||||
SetHookData['HookParameters']
|
||||
>((acc, [_, { label, value }]) => {
|
||||
return acc.concat({
|
||||
HookParameter: { HookParameterName: toHex(label), HookParameterValue: toHex(value) }
|
||||
})
|
||||
}, [])
|
||||
const Memos = memos
|
||||
? Object.entries(memos).reduce<SetHookData['Memos']>((acc, [_, { format, data, type }]) => {
|
||||
return acc?.concat({
|
||||
Memo: { MemoData: toHex(data), MemoFormat: toHex(format), MemoType: toHex(type) }
|
||||
})
|
||||
}, [])
|
||||
: undefined
|
||||
|
||||
return prepareTransaction({
|
||||
...txFields,
|
||||
HookParameters,
|
||||
Flags,
|
||||
TransactionType,
|
||||
Destination,
|
||||
Account
|
||||
Account,
|
||||
Memos
|
||||
})
|
||||
},
|
||||
[txState]
|
||||
@@ -69,15 +93,29 @@ const Transaction: FC<TransactionProps> = ({ header, state: txState, ...props })
|
||||
}
|
||||
}, [selectedAccount?.value, selectedTransaction?.value, setState, txIsLoading])
|
||||
|
||||
const getJsonString = useCallback(
|
||||
(state?: Partial<TransactionState>) =>
|
||||
JSON.stringify(prepareOptions?.(state) || {}, null, editorSettings.tabSize),
|
||||
[editorSettings.tabSize, prepareOptions]
|
||||
)
|
||||
|
||||
const saveEditorState = useCallback(
|
||||
(value: string = '', transactionType?: string) => {
|
||||
const pTx = prepareState(value, transactionType)
|
||||
if (pTx) {
|
||||
pTx.editorValue = getJsonString(pTx)
|
||||
return setState(pTx)
|
||||
}
|
||||
},
|
||||
[getJsonString, setState]
|
||||
)
|
||||
|
||||
const submitTest = useCallback(async () => {
|
||||
let st: TransactionState | undefined
|
||||
const tt = txState.selectedTransaction?.value
|
||||
if (viewType === 'json') {
|
||||
// save the editor state first
|
||||
const pst = prepareState(editorValue || '', tt)
|
||||
if (!pst) return
|
||||
|
||||
st = setState(pst)
|
||||
st = saveEditorState(editorValue, tt)
|
||||
if (!st) return
|
||||
}
|
||||
|
||||
const account = accounts.find(acc => acc.address === selectedAccount?.value)
|
||||
@@ -108,23 +146,18 @@ const Transaction: FC<TransactionProps> = ({ header, state: txState, ...props })
|
||||
}
|
||||
setState({ txIsLoading: false })
|
||||
}, [
|
||||
txState.selectedTransaction?.value,
|
||||
viewType,
|
||||
accounts,
|
||||
txIsDisabled,
|
||||
setState,
|
||||
header,
|
||||
saveEditorState,
|
||||
editorValue,
|
||||
txState,
|
||||
selectedAccount?.value,
|
||||
prepareOptions
|
||||
])
|
||||
|
||||
const getJsonString = useCallback(
|
||||
(state?: Partial<TransactionState>) =>
|
||||
JSON.stringify(prepareOptions?.(state) || {}, null, editorSettings.tabSize),
|
||||
[editorSettings.tabSize, prepareOptions]
|
||||
)
|
||||
|
||||
const resetState = useCallback(
|
||||
(transactionType: SelectOption | undefined = defaultTransactionType) => {
|
||||
const fields = getTxFields(transactionType?.value)
|
||||
@@ -177,11 +210,21 @@ const Transaction: FC<TransactionProps> = ({ header, state: txState, ...props })
|
||||
[accounts, prepareOptions, setState, txState]
|
||||
)
|
||||
|
||||
const switchToJson = useCallback(() => {
|
||||
const editorValue = getJsonString()
|
||||
setState({ viewType: 'json', editorValue })
|
||||
}, [getJsonString, setState])
|
||||
|
||||
const switchToUI = useCallback(() => {
|
||||
setState({ viewType: 'ui' })
|
||||
}, [setState])
|
||||
|
||||
return (
|
||||
<Box css={{ position: 'relative', height: 'calc(100% - 28px)' }} {...props}>
|
||||
{viewType === 'json' ? (
|
||||
<TxJson
|
||||
getJsonString={getJsonString}
|
||||
saveEditorState={saveEditorState}
|
||||
header={header}
|
||||
state={txState}
|
||||
setState={setState}
|
||||
@@ -189,6 +232,7 @@ const Transaction: FC<TransactionProps> = ({ header, state: txState, ...props })
|
||||
/>
|
||||
) : (
|
||||
<TxUI
|
||||
switchToJson={switchToJson}
|
||||
state={txState}
|
||||
resetState={resetState}
|
||||
setState={setState}
|
||||
@@ -209,8 +253,8 @@ const Transaction: FC<TransactionProps> = ({ header, state: txState, ...props })
|
||||
<Button
|
||||
onClick={() => {
|
||||
if (viewType === 'ui') {
|
||||
setState({ viewType: 'json' })
|
||||
} else setState({ viewType: 'ui' })
|
||||
switchToJson()
|
||||
} else switchToUI()
|
||||
}}
|
||||
outline
|
||||
>
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { FC, useCallback, useEffect, useMemo, useState } from 'react'
|
||||
import { FC, useCallback, useEffect, useState } from 'react'
|
||||
import { useSnapshot } from 'valtio'
|
||||
import state, { prepareState, transactionsData, TransactionState } from '../../state'
|
||||
import state, { transactionsData, TransactionState } from '../../state'
|
||||
import Text from '../Text'
|
||||
import { Flex, Link } from '..'
|
||||
import { showAlert } from '../../state/actions/showAlert'
|
||||
@@ -11,27 +11,27 @@ import Monaco from '../Monaco'
|
||||
import type monaco from 'monaco-editor'
|
||||
|
||||
interface JsonProps {
|
||||
getJsonString?: (state?: Partial<TransactionState>) => string
|
||||
getJsonString: (st?: Partial<TransactionState>) => string
|
||||
saveEditorState: (val?: string, tt?: string) => TransactionState | undefined
|
||||
header?: string
|
||||
setState: (pTx?: Partial<TransactionState> | undefined) => void
|
||||
state: TransactionState
|
||||
estimateFee?: () => Promise<string | undefined>
|
||||
}
|
||||
|
||||
export const TxJson: FC<JsonProps> = ({ getJsonString, state: txState, header, setState }) => {
|
||||
export const TxJson: FC<JsonProps> = ({
|
||||
getJsonString,
|
||||
state: txState,
|
||||
header,
|
||||
setState,
|
||||
saveEditorState
|
||||
}) => {
|
||||
const { editorSettings, accounts } = useSnapshot(state)
|
||||
const { editorValue, estimatedFee } = txState
|
||||
const { editorValue, estimatedFee, editorIsSaved } = txState
|
||||
const [currTxType, setCurrTxType] = useState<string | undefined>(
|
||||
txState.selectedTransaction?.value
|
||||
)
|
||||
|
||||
useEffect(() => {
|
||||
setState({
|
||||
editorValue: getJsonString?.()
|
||||
})
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [])
|
||||
|
||||
useEffect(() => {
|
||||
const parsed = parseJSON(editorValue)
|
||||
if (!parsed) return
|
||||
@@ -44,29 +44,19 @@ export const TxJson: FC<JsonProps> = ({ getJsonString, state: txState, header, s
|
||||
}
|
||||
}, [editorValue])
|
||||
|
||||
const saveState = (value: string, transactionType?: string) => {
|
||||
const tx = prepareState(value, transactionType)
|
||||
if (tx) {
|
||||
setState(tx)
|
||||
setState({
|
||||
editorValue: getJsonString?.(tx)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
const discardChanges = () => {
|
||||
showAlert('Confirm', {
|
||||
body: 'Are you sure to discard these changes?',
|
||||
confirmText: 'Yes',
|
||||
onCancel: () => {},
|
||||
onConfirm: () => setState({ editorValue: getJsonString?.() })
|
||||
onConfirm: () => setState({ editorValue: getJsonString() })
|
||||
})
|
||||
}
|
||||
|
||||
const onExit = (value: string) => {
|
||||
const options = parseJSON(value)
|
||||
if (options) {
|
||||
saveState(value, currTxType)
|
||||
saveEditorState(value, currTxType)
|
||||
return
|
||||
}
|
||||
showAlert('Error!', {
|
||||
@@ -163,8 +153,6 @@ export const TxJson: FC<JsonProps> = ({ getJsonString, state: txState, header, s
|
||||
})
|
||||
}, [getSchemas, monacoInst])
|
||||
|
||||
const hasUnsaved = useMemo(() => editorValue !== getJsonString?.(), [editorValue, getJsonString])
|
||||
|
||||
return (
|
||||
<Monaco
|
||||
rootProps={{
|
||||
@@ -174,7 +162,7 @@ export const TxJson: FC<JsonProps> = ({ getJsonString, state: txState, header, s
|
||||
id={header}
|
||||
height="100%"
|
||||
value={editorValue}
|
||||
onChange={val => setState({ editorValue: val })}
|
||||
onChange={val => setState({ editorValue: val, editorIsSaved: false })}
|
||||
onMount={(editor, monaco) => {
|
||||
editor.updateOptions({
|
||||
minimap: { enabled: false },
|
||||
@@ -190,12 +178,12 @@ export const TxJson: FC<JsonProps> = ({ getJsonString, state: txState, header, s
|
||||
model?.onWillDispose(() => onExit(model.getValue()))
|
||||
}}
|
||||
overlay={
|
||||
hasUnsaved ? (
|
||||
!editorIsSaved ? (
|
||||
<Flex row align="center" css={{ fontSize: '$xs', color: '$textMuted', ml: 'auto' }}>
|
||||
<Text muted small>
|
||||
This file has unsaved changes.
|
||||
</Text>
|
||||
<Link css={{ ml: '$1' }} onClick={() => saveState(editorValue || '', currTxType)}>
|
||||
<Link css={{ ml: '$1' }} onClick={() => saveEditorState(editorValue, currTxType)}>
|
||||
save
|
||||
</Link>
|
||||
<Link css={{ ml: '$1' }} onClick={discardChanges}>
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { FC, useCallback, useEffect, useMemo, useState } from 'react'
|
||||
import { FC, ReactNode, useCallback, useEffect, useMemo, useState } from 'react'
|
||||
import Container from '../Container'
|
||||
import Flex from '../Flex'
|
||||
import Input from '../Input'
|
||||
@@ -18,18 +18,34 @@ import { streamState } from '../DebugStream'
|
||||
import { Button } from '..'
|
||||
import Textarea from '../Textarea'
|
||||
import { getFlags } from '../../state/constants/flags'
|
||||
import { Plus, Trash } from 'phosphor-react'
|
||||
import AccountSequence from '../Sequence'
|
||||
|
||||
interface UIProps {
|
||||
setState: (pTx?: Partial<TransactionState> | undefined) => TransactionState | undefined
|
||||
resetState: (tt?: SelectOption) => TransactionState | undefined
|
||||
state: TransactionState
|
||||
estimateFee?: (...arg: any) => Promise<string | undefined>
|
||||
switchToJson: () => void
|
||||
}
|
||||
|
||||
export const TxUI: FC<UIProps> = ({ state: txState, setState, resetState, estimateFee }) => {
|
||||
export const TxUI: FC<UIProps> = ({
|
||||
state: txState,
|
||||
setState,
|
||||
resetState,
|
||||
estimateFee,
|
||||
switchToJson
|
||||
}) => {
|
||||
const { accounts } = useSnapshot(state)
|
||||
const { selectedAccount, selectedDestAccount, selectedTransaction, txFields, selectedFlags } =
|
||||
txState
|
||||
const {
|
||||
selectedAccount,
|
||||
selectedDestAccount,
|
||||
selectedTransaction,
|
||||
txFields,
|
||||
selectedFlags,
|
||||
hookParameters,
|
||||
memos
|
||||
} = txState
|
||||
|
||||
const accountOptions: SelectOption[] = accounts.map(acc => ({
|
||||
label: acc.name,
|
||||
@@ -94,8 +110,6 @@ export const TxUI: FC<UIProps> = ({ state: txState, setState, resetState, estima
|
||||
[handleEstimateFee, resetState, setState]
|
||||
)
|
||||
|
||||
const switchToJson = () => setState({ viewType: 'json' })
|
||||
|
||||
// default tx
|
||||
useEffect(() => {
|
||||
if (selectedTransaction?.value) return
|
||||
@@ -110,7 +124,7 @@ export const TxUI: FC<UIProps> = ({ state: txState, setState, resetState, estima
|
||||
[selectedTransaction?.value]
|
||||
)
|
||||
|
||||
const richFields = ['TransactionType', 'Account']
|
||||
const richFields = ['TransactionType', 'Account', 'HookParameters', 'Memos']
|
||||
if (fields.Destination !== undefined) {
|
||||
richFields.push('Destination')
|
||||
}
|
||||
@@ -120,7 +134,6 @@ export const TxUI: FC<UIProps> = ({ state: txState, setState, resetState, estima
|
||||
}
|
||||
|
||||
const otherFields = Object.keys(txFields).filter(k => !richFields.includes(k)) as [keyof TxFields]
|
||||
|
||||
return (
|
||||
<Container
|
||||
css={{
|
||||
@@ -130,94 +143,44 @@ export const TxUI: FC<UIProps> = ({ state: txState, setState, resetState, estima
|
||||
}}
|
||||
>
|
||||
<Flex column fluid css={{ height: '100%', overflowY: 'auto', pr: '$1' }}>
|
||||
<Flex
|
||||
row
|
||||
fluid
|
||||
css={{
|
||||
justifyContent: 'flex-end',
|
||||
alignItems: 'center',
|
||||
mb: '$3',
|
||||
mt: '1px',
|
||||
pr: '1px'
|
||||
}}
|
||||
>
|
||||
<Text muted css={{ mr: '$3' }}>
|
||||
Transaction type:{' '}
|
||||
</Text>
|
||||
<TxField label="Transaction type">
|
||||
<Select
|
||||
instanceId="transactionsType"
|
||||
placeholder="Select transaction type"
|
||||
options={transactionsOptions}
|
||||
hideSelectedOptions
|
||||
css={{ width: '70%' }}
|
||||
value={selectedTransaction}
|
||||
onChange={(tt: any) => handleChangeTxType(tt)}
|
||||
/>
|
||||
</Flex>
|
||||
<Flex
|
||||
row
|
||||
fluid
|
||||
css={{
|
||||
justifyContent: 'flex-end',
|
||||
alignItems: 'center',
|
||||
mb: '$3',
|
||||
pr: '1px'
|
||||
}}
|
||||
>
|
||||
<Text muted css={{ mr: '$3' }}>
|
||||
Account:{' '}
|
||||
</Text>
|
||||
</TxField>
|
||||
<TxField label="Account">
|
||||
<Select
|
||||
instanceId="from-account"
|
||||
placeholder="Select your account"
|
||||
css={{ width: '70%' }}
|
||||
options={accountOptions}
|
||||
value={selectedAccount}
|
||||
onChange={(acc: any) => handleSetAccount(acc)} // TODO make react-select have correct types for acc
|
||||
/>
|
||||
</Flex>
|
||||
</TxField>
|
||||
<TxField label="Sequence">
|
||||
<AccountSequence address={selectedAccount?.value} />
|
||||
</TxField>
|
||||
{richFields.includes('Destination') && (
|
||||
<Flex
|
||||
row
|
||||
fluid
|
||||
css={{
|
||||
justifyContent: 'flex-end',
|
||||
alignItems: 'center',
|
||||
mb: '$3',
|
||||
pr: '1px'
|
||||
}}
|
||||
>
|
||||
<Text muted css={{ mr: '$3', textAlign: 'end' }}>
|
||||
Destination account:{' '}
|
||||
</Text>
|
||||
<TxField label="Destination account">
|
||||
<Select
|
||||
instanceId="to-account"
|
||||
placeholder="Select the destination account"
|
||||
css={{ width: '70%' }}
|
||||
options={destAccountOptions}
|
||||
value={selectedDestAccount}
|
||||
isClearable
|
||||
onChange={(acc: any) => setState({ selectedDestAccount: acc })}
|
||||
/>
|
||||
</Flex>
|
||||
</TxField>
|
||||
)}
|
||||
{richFields.includes('Flags') && (
|
||||
<Flex
|
||||
row
|
||||
fluid
|
||||
css={{
|
||||
justifyContent: 'flex-end',
|
||||
alignItems: 'center',
|
||||
mb: '$3',
|
||||
pr: '1px'
|
||||
}}
|
||||
>
|
||||
<Text muted css={{ mr: '$3' }}>
|
||||
Flags:{' '}
|
||||
</Text>
|
||||
<TxField label="Flags">
|
||||
<Select
|
||||
isClearable
|
||||
css={{ width: '70%' }}
|
||||
instanceId="flags"
|
||||
placeholder="Select flags to apply"
|
||||
menuPosition="fixed"
|
||||
@@ -229,7 +192,7 @@ export const TxUI: FC<UIProps> = ({ state: txState, setState, resetState, estima
|
||||
selectedFlags ? selectedFlags.length >= flagsOptions.length - 1 : false
|
||||
}
|
||||
/>
|
||||
</Flex>
|
||||
</TxField>
|
||||
)}
|
||||
{otherFields.map(field => {
|
||||
let _value = txFields[field]
|
||||
@@ -251,93 +214,239 @@ export const TxUI: FC<UIProps> = ({ state: txState, setState, resetState, estima
|
||||
let rows = isJson ? (value?.match(/\n/gm)?.length || 0) + 1 : undefined
|
||||
if (rows && rows > 5) rows = 5
|
||||
return (
|
||||
<Flex column key={field} css={{ mb: '$2', pr: '1px' }}>
|
||||
<Flex
|
||||
row
|
||||
fluid
|
||||
css={{
|
||||
justifyContent: 'flex-end',
|
||||
alignItems: 'center',
|
||||
position: 'relative'
|
||||
}}
|
||||
>
|
||||
<Text muted css={{ mr: '$3' }}>
|
||||
{field + (isXrp ? ' (XRP)' : '')}:{' '}
|
||||
</Text>
|
||||
{isJson ? (
|
||||
<Textarea
|
||||
rows={rows}
|
||||
value={value}
|
||||
spellCheck={false}
|
||||
onChange={switchToJson}
|
||||
css={{
|
||||
width: '70%',
|
||||
flex: 'inherit',
|
||||
resize: 'vertical'
|
||||
}}
|
||||
/>
|
||||
) : (
|
||||
<Input
|
||||
type={isFee ? 'number' : 'text'}
|
||||
value={value}
|
||||
onChange={e => {
|
||||
if (isFee) {
|
||||
const val = e.target.value.replaceAll('.', '').replaceAll(',', '')
|
||||
handleSetField(field, val)
|
||||
} else {
|
||||
handleSetField(field, e.target.value)
|
||||
}
|
||||
}}
|
||||
onKeyPress={
|
||||
isFee
|
||||
? e => {
|
||||
if (e.key === '.' || e.key === ',') {
|
||||
e.preventDefault()
|
||||
}
|
||||
}
|
||||
: undefined
|
||||
<TxField key={field} label={field + (isXrp ? ' (XRP)' : '')}>
|
||||
{isJson ? (
|
||||
<Textarea
|
||||
rows={rows}
|
||||
value={value}
|
||||
spellCheck={false}
|
||||
onChange={switchToJson}
|
||||
css={{
|
||||
flex: 'inherit',
|
||||
resize: 'vertical'
|
||||
}}
|
||||
/>
|
||||
) : (
|
||||
<Input
|
||||
type={isFee ? 'number' : 'text'}
|
||||
value={value}
|
||||
onChange={e => {
|
||||
if (isFee) {
|
||||
const val = e.target.value.replaceAll('.', '').replaceAll(',', '')
|
||||
handleSetField(field, val)
|
||||
} else {
|
||||
handleSetField(field, e.target.value)
|
||||
}
|
||||
css={{
|
||||
width: '70%',
|
||||
flex: 'inherit',
|
||||
'-moz-appearance': 'textfield',
|
||||
'&::-webkit-outer-spin-button': {
|
||||
'-webkit-appearance': 'none',
|
||||
margin: 0
|
||||
},
|
||||
'&::-webkit-inner-spin-button ': {
|
||||
'-webkit-appearance': 'none',
|
||||
margin: 0
|
||||
}
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
{isFee && (
|
||||
<Button
|
||||
size="xs"
|
||||
variant="primary"
|
||||
outline
|
||||
disabled={txState.txIsDisabled}
|
||||
isDisabled={txState.txIsDisabled}
|
||||
isLoading={feeLoading}
|
||||
css={{
|
||||
position: 'absolute',
|
||||
right: '$2',
|
||||
fontSize: '$xs',
|
||||
cursor: 'pointer',
|
||||
alignContent: 'center',
|
||||
display: 'flex'
|
||||
}}
|
||||
onClick={() => handleEstimateFee()}
|
||||
>
|
||||
Suggest
|
||||
</Button>
|
||||
)}
|
||||
</Flex>
|
||||
</Flex>
|
||||
}}
|
||||
onKeyPress={
|
||||
isFee
|
||||
? e => {
|
||||
if (e.key === '.' || e.key === ',') {
|
||||
e.preventDefault()
|
||||
}
|
||||
}
|
||||
: undefined
|
||||
}
|
||||
css={{
|
||||
flex: 'inherit',
|
||||
'-moz-appearance': 'textfield',
|
||||
'&::-webkit-outer-spin-button': {
|
||||
'-webkit-appearance': 'none',
|
||||
margin: 0
|
||||
},
|
||||
'&::-webkit-inner-spin-button ': {
|
||||
'-webkit-appearance': 'none',
|
||||
margin: 0
|
||||
}
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
{isFee && (
|
||||
<Button
|
||||
size="xs"
|
||||
variant="primary"
|
||||
outline
|
||||
disabled={txState.txIsDisabled}
|
||||
isDisabled={txState.txIsDisabled}
|
||||
isLoading={feeLoading}
|
||||
css={{
|
||||
position: 'absolute',
|
||||
right: '$2',
|
||||
fontSize: '$xs',
|
||||
cursor: 'pointer',
|
||||
alignContent: 'center',
|
||||
display: 'flex'
|
||||
}}
|
||||
onClick={() => handleEstimateFee()}
|
||||
>
|
||||
Suggest
|
||||
</Button>
|
||||
)}
|
||||
</TxField>
|
||||
)
|
||||
})}
|
||||
<TxField multiLine label="Hook parameters">
|
||||
<Flex column fluid>
|
||||
{Object.entries(hookParameters).map(([id, { label, value }]) => (
|
||||
<Flex column key={id} css={{ mb: '$2' }}>
|
||||
<Flex row>
|
||||
<Input
|
||||
placeholder="Parameter name"
|
||||
value={label}
|
||||
onChange={e => {
|
||||
setState({
|
||||
hookParameters: {
|
||||
...hookParameters,
|
||||
[id]: { label: e.target.value, value }
|
||||
}
|
||||
})
|
||||
}}
|
||||
/>
|
||||
<Input
|
||||
css={{ mx: '$2' }}
|
||||
placeholder="Value"
|
||||
value={value}
|
||||
onChange={e => {
|
||||
setState({
|
||||
hookParameters: {
|
||||
...hookParameters,
|
||||
[id]: { label, value: e.target.value }
|
||||
}
|
||||
})
|
||||
}}
|
||||
/>
|
||||
<Button
|
||||
onClick={() => {
|
||||
const { [id]: _, ...rest } = hookParameters
|
||||
setState({ hookParameters: rest })
|
||||
}}
|
||||
variant="destroy"
|
||||
>
|
||||
<Trash weight="regular" size="16px" />
|
||||
</Button>
|
||||
</Flex>
|
||||
</Flex>
|
||||
))}
|
||||
<Button
|
||||
outline
|
||||
fullWidth
|
||||
type="button"
|
||||
onClick={() => {
|
||||
const id = Object.keys(hookParameters).length
|
||||
setState({
|
||||
hookParameters: { ...hookParameters, [id]: { label: '', value: '' } }
|
||||
})
|
||||
}}
|
||||
>
|
||||
<Plus size="16px" />
|
||||
Add Hook Parameter
|
||||
</Button>
|
||||
</Flex>
|
||||
</TxField>
|
||||
<TxField multiLine label="Memos">
|
||||
<Flex column fluid>
|
||||
{Object.entries(memos).map(([id, memo]) => (
|
||||
<Flex column key={id} css={{ mb: '$2' }}>
|
||||
<Flex
|
||||
row
|
||||
css={{
|
||||
flexWrap: 'wrap',
|
||||
width: '100%'
|
||||
}}
|
||||
>
|
||||
<Input
|
||||
placeholder="Memo type"
|
||||
value={memo.type}
|
||||
onChange={e => {
|
||||
setState({
|
||||
memos: {
|
||||
...memos,
|
||||
[id]: { ...memo, type: e.target.value }
|
||||
}
|
||||
})
|
||||
}}
|
||||
/>
|
||||
<Input
|
||||
placeholder="Data"
|
||||
css={{ mx: '$2' }}
|
||||
value={memo.data}
|
||||
onChange={e => {
|
||||
setState({
|
||||
memos: {
|
||||
...memos,
|
||||
[id]: { ...memo, data: e.target.value }
|
||||
}
|
||||
})
|
||||
}}
|
||||
/>
|
||||
<Input
|
||||
placeholder="Format"
|
||||
value={memo.format}
|
||||
onChange={e => {
|
||||
setState({
|
||||
memos: {
|
||||
...memos,
|
||||
[id]: { ...memo, format: e.target.value }
|
||||
}
|
||||
})
|
||||
}}
|
||||
/>
|
||||
<Button
|
||||
css={{ ml: '$2' }}
|
||||
onClick={() => {
|
||||
const { [id]: _, ...rest } = memos
|
||||
setState({ memos: rest })
|
||||
}}
|
||||
variant="destroy"
|
||||
>
|
||||
<Trash weight="regular" size="16px" />
|
||||
</Button>
|
||||
</Flex>
|
||||
</Flex>
|
||||
))}
|
||||
<Button
|
||||
outline
|
||||
fullWidth
|
||||
type="button"
|
||||
onClick={() => {
|
||||
const id = Object.keys(memos).length
|
||||
setState({
|
||||
memos: { ...memos, [id]: { data: '', format: '', type: '' } }
|
||||
})
|
||||
}}
|
||||
>
|
||||
<Plus size="16px" />
|
||||
Add Memo
|
||||
</Button>
|
||||
</Flex>
|
||||
</TxField>
|
||||
</Flex>
|
||||
</Container>
|
||||
)
|
||||
}
|
||||
|
||||
export const TxField: FC<{ label: string; children: ReactNode; multiLine?: boolean }> = ({
|
||||
label,
|
||||
children,
|
||||
multiLine = false
|
||||
}) => {
|
||||
return (
|
||||
<Flex
|
||||
row
|
||||
fluid
|
||||
css={{
|
||||
justifyContent: 'flex-end',
|
||||
alignItems: multiLine ? 'flex-start' : 'center',
|
||||
position: 'relative',
|
||||
mb: '$3',
|
||||
mt: '1px',
|
||||
pr: '1px'
|
||||
}}
|
||||
>
|
||||
<Text muted css={{ mr: '$3', mt: multiLine ? '$2' : 0 }}>
|
||||
{label}:{' '}
|
||||
</Text>
|
||||
<Flex css={{ width: '70%', alignItems: 'center' }}>{children}</Flex>
|
||||
</Flex>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -124,7 +124,8 @@
|
||||
},
|
||||
"Flags": "1",
|
||||
"Destination": "",
|
||||
"Fee": "10"
|
||||
"Fee": "10",
|
||||
"Owner": ""
|
||||
},
|
||||
{
|
||||
"TransactionType": "OfferCancel",
|
||||
@@ -245,5 +246,14 @@
|
||||
}
|
||||
},
|
||||
"Sequence": 12
|
||||
},
|
||||
{
|
||||
"TransactionType": "Invoke",
|
||||
"Fee": "12"
|
||||
},
|
||||
{
|
||||
"TransactionType": "UriToken",
|
||||
"Fee": "12",
|
||||
"URI": "697066733A2F2F516D614374444B5A4656767666756676626479346573745A626851483744586831364354707631686F776D424779"
|
||||
}
|
||||
]
|
||||
@@ -12,7 +12,7 @@ module.exports = {
|
||||
config.resolve.fallback.fs = false
|
||||
}
|
||||
config.module.rules.push({
|
||||
test: /\.md$/,
|
||||
test: [/\.md$/, /hook-bundle\.js$/],
|
||||
use: 'raw-loader'
|
||||
})
|
||||
return config
|
||||
|
||||
@@ -8,7 +8,8 @@
|
||||
"start": "next start",
|
||||
"lint": "next lint",
|
||||
"format": "prettier --write .",
|
||||
"postinstall": "patch-package"
|
||||
"postinstall": "patch-package && yarn run postinstall-postinstall",
|
||||
"postinstall-postinstall": "./node_modules/.bin/browserify -r ripple-binary-codec -r ripple-keypairs -r ripple-address-codec -r ripple-secret-codec -r ./node_modules/xrpl-accountlib/dist/index.js:xrpl-accountlib -o node_modules/xrpl-accountlib/dist/browser.hook-bundle.js"
|
||||
},
|
||||
"dependencies": {
|
||||
"@codingame/monaco-jsonrpc": "^0.3.1",
|
||||
@@ -65,8 +66,8 @@
|
||||
"vscode-languageserver": "^7.0.0",
|
||||
"vscode-uri": "^3.0.2",
|
||||
"wabt": "^1.0.30",
|
||||
"xrpl-accountlib": "^1.5.2",
|
||||
"xrpl-client": "^1.9.4"
|
||||
"xrpl-accountlib": "^1.6.1",
|
||||
"xrpl-client": "^2.0.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/dinero.js": "^1.9.0",
|
||||
@@ -75,6 +76,7 @@
|
||||
"@types/lodash.xor": "^4.5.6",
|
||||
"@types/pako": "^1.0.2",
|
||||
"@types/react": "17.0.31",
|
||||
"browserify": "^17.0.0",
|
||||
"eslint": "7.32.0",
|
||||
"eslint-config-next": "11.1.2",
|
||||
"raw-loader": "^4.0.2",
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
5
raw-loader.d.ts
vendored
5
raw-loader.d.ts
vendored
@@ -2,3 +2,8 @@ declare module '*.md' {
|
||||
const content: string
|
||||
export default content
|
||||
}
|
||||
|
||||
declare module '*.hook-bundle.js' {
|
||||
const content: string
|
||||
export default content
|
||||
}
|
||||
@@ -1,5 +1,6 @@
|
||||
import toast from 'react-hot-toast'
|
||||
import state, { FaucetAccountRes } from '../index'
|
||||
import fetchAccountInfo from '../../utils/accountInfo';
|
||||
|
||||
export const names = [
|
||||
'Alice',
|
||||
@@ -35,40 +36,37 @@ export const addFaucetAccount = async (name?: string, showToast: boolean = false
|
||||
})
|
||||
const json: FaucetAccountRes | { error: string } = await res.json()
|
||||
if ('error' in json) {
|
||||
if (showToast) {
|
||||
return toast.error(json.error, { id: toastId })
|
||||
} else {
|
||||
return
|
||||
}
|
||||
} else {
|
||||
if (showToast) {
|
||||
toast.success('New account created', { id: toastId })
|
||||
}
|
||||
const currNames = state.accounts.map(acc => acc.name)
|
||||
state.accounts.push({
|
||||
name: name || names.filter(name => !currNames.includes(name))[0],
|
||||
xrp: (json.xrp || 0 * 1000000).toString(),
|
||||
address: json.address,
|
||||
secret: json.secret,
|
||||
sequence: 1,
|
||||
hooks: [],
|
||||
isLoading: false,
|
||||
version: '2'
|
||||
})
|
||||
if (!showToast) return;
|
||||
return toast.error(json.error, { id: toastId })
|
||||
}
|
||||
const currNames = state.accounts.map(acc => acc.name)
|
||||
const info = await fetchAccountInfo(json.address, { silent: true })
|
||||
state.accounts.push({
|
||||
name: name || names.filter(name => !currNames.includes(name))[0],
|
||||
xrp: (json.xrp || 0 * 1000000).toString(),
|
||||
address: json.address,
|
||||
secret: json.secret,
|
||||
sequence: info?.Sequence || 1,
|
||||
hooks: [],
|
||||
isLoading: false,
|
||||
version: '2'
|
||||
})
|
||||
if (showToast) {
|
||||
toast.success('New account created', { id: toastId })
|
||||
}
|
||||
}
|
||||
|
||||
// fetch initial faucets
|
||||
;(async function fetchFaucets() {
|
||||
if (typeof window !== 'undefined') {
|
||||
if (state.accounts.length === 0) {
|
||||
await addFaucetAccount()
|
||||
// setTimeout(() => {
|
||||
// addFaucetAccount();
|
||||
// }, 10000);
|
||||
// fetch initial faucets
|
||||
; (async function fetchFaucets() {
|
||||
if (typeof window !== 'undefined') {
|
||||
if (state.accounts.length === 0) {
|
||||
await addFaucetAccount()
|
||||
// setTimeout(() => {
|
||||
// addFaucetAccount();
|
||||
// }, 10000);
|
||||
}
|
||||
}
|
||||
}
|
||||
})()
|
||||
})()
|
||||
|
||||
export const addFunds = async (address: string) => {
|
||||
const toastId = toast.loading('Requesting funds')
|
||||
|
||||
@@ -6,8 +6,9 @@ import calculateHookOn, { TTS } from '../../utils/hookOnCalculator'
|
||||
import { Link } from '../../components'
|
||||
import { ref } from 'valtio'
|
||||
import estimateFee from '../../utils/estimateFee'
|
||||
import { SetHookData } from '../../utils/setHook'
|
||||
import { SetHookData, toHex } from '../../utils/setHook'
|
||||
import ResultLink from '../../components/ResultLink'
|
||||
import { xrplSend } from './xrpl-client'
|
||||
|
||||
export const sha256 = async (string: string) => {
|
||||
const utf8 = new TextEncoder().encode(string)
|
||||
@@ -17,13 +18,6 @@ export const sha256 = async (string: string) => {
|
||||
return hashHex
|
||||
}
|
||||
|
||||
function toHex(str: string) {
|
||||
var result = ''
|
||||
for (var i = 0; i < str.length; i++) {
|
||||
result += str.charCodeAt(i).toString(16)
|
||||
}
|
||||
return result.toUpperCase()
|
||||
}
|
||||
|
||||
function arrayBufferToHex(arrayBuffer?: ArrayBuffer | null) {
|
||||
if (!arrayBuffer) {
|
||||
@@ -64,9 +58,6 @@ export const prepareDeployHookTx = async (
|
||||
if (!activeFile?.compiledContent) {
|
||||
return
|
||||
}
|
||||
if (!state.client) {
|
||||
return
|
||||
}
|
||||
const HookNamespace = (await sha256(data.HookNamespace)).toUpperCase()
|
||||
const hookOnValues: (keyof TTS)[] = data.Invoke.map(tt => tt.value)
|
||||
const { HookParameters } = data
|
||||
@@ -87,196 +78,184 @@ export const prepareDeployHookTx = async (
|
||||
// }
|
||||
// }
|
||||
// });
|
||||
if (typeof window !== 'undefined') {
|
||||
const tx = {
|
||||
Account: account.address,
|
||||
TransactionType: 'SetHook',
|
||||
Sequence: account.sequence,
|
||||
Fee: data.Fee,
|
||||
Hooks: [
|
||||
{
|
||||
Hook: {
|
||||
CreateCode: arrayBufferToHex(activeFile?.compiledContent).toUpperCase(),
|
||||
HookOn: calculateHookOn(hookOnValues),
|
||||
HookNamespace,
|
||||
HookApiVersion: 0,
|
||||
Flags: 1,
|
||||
// ...(filteredHookGrants.length > 0 && { HookGrants: filteredHookGrants }),
|
||||
...(filteredHookParameters.length > 0 && {
|
||||
HookParameters: filteredHookParameters
|
||||
})
|
||||
}
|
||||
if (typeof window === 'undefined') return
|
||||
const tx = {
|
||||
Account: account.address,
|
||||
TransactionType: 'SetHook',
|
||||
Sequence: account.sequence,
|
||||
Fee: data.Fee,
|
||||
NetworkID: process.env.NEXT_PUBLIC_NETWORK_ID,
|
||||
Hooks: [
|
||||
{
|
||||
Hook: {
|
||||
CreateCode: arrayBufferToHex(activeFile?.compiledContent).toUpperCase(),
|
||||
HookOn: calculateHookOn(hookOnValues),
|
||||
HookNamespace,
|
||||
HookApiVersion: 0,
|
||||
Flags: 1,
|
||||
// ...(filteredHookGrants.length > 0 && { HookGrants: filteredHookGrants }),
|
||||
...(filteredHookParameters.length > 0 && {
|
||||
HookParameters: filteredHookParameters
|
||||
})
|
||||
}
|
||||
]
|
||||
}
|
||||
return tx
|
||||
}
|
||||
]
|
||||
}
|
||||
return tx
|
||||
}
|
||||
|
||||
/* deployHook function turns the wasm binary into
|
||||
* hex string, signs the transaction and deploys it to
|
||||
* Hooks testnet.
|
||||
/*
|
||||
* Turns the wasm binary into hex string, signs the transaction and deploys it to Hooks testnet.
|
||||
*/
|
||||
export const deployHook = async (account: IAccount & { name?: string }, data: SetHookData) => {
|
||||
if (typeof window !== 'undefined') {
|
||||
const activeFile = state.files[state.active]?.compiledContent
|
||||
? state.files[state.active]
|
||||
: state.files.filter(file => file.compiledContent)[0]
|
||||
state.deployValues[activeFile.name] = data
|
||||
const tx = await prepareDeployHookTx(account, data)
|
||||
if (!tx) {
|
||||
return
|
||||
}
|
||||
if (!state.client) {
|
||||
return
|
||||
}
|
||||
const keypair = derive.familySeed(account.secret)
|
||||
const activeFile = state.files[state.active]?.compiledContent
|
||||
? state.files[state.active]
|
||||
: state.files.filter(file => file.compiledContent)[0]
|
||||
state.deployValues[activeFile.name] = data
|
||||
|
||||
const { signedTransaction } = sign(tx, keypair)
|
||||
const currentAccount = state.accounts.find(acc => acc.address === account.address)
|
||||
if (currentAccount) {
|
||||
currentAccount.isLoading = true
|
||||
}
|
||||
let submitRes
|
||||
const tx = await prepareDeployHookTx(account, data)
|
||||
if (!tx) {
|
||||
return
|
||||
}
|
||||
const keypair = derive.familySeed(account.secret)
|
||||
const { signedTransaction } = sign(tx, keypair)
|
||||
|
||||
try {
|
||||
submitRes = await state.client?.send({
|
||||
command: 'submit',
|
||||
tx_blob: signedTransaction
|
||||
const currentAccount = state.accounts.find(acc => acc.address === account.address)
|
||||
if (currentAccount) {
|
||||
currentAccount.isLoading = true
|
||||
}
|
||||
|
||||
let submitRes
|
||||
try {
|
||||
submitRes = await xrplSend({
|
||||
command: 'submit',
|
||||
tx_blob: signedTransaction
|
||||
})
|
||||
|
||||
const txHash = submitRes.tx_json?.hash
|
||||
const resultMsg = ref(
|
||||
<>
|
||||
[<ResultLink result={submitRes.engine_result} />] {submitRes.engine_result_message}{' '}
|
||||
{txHash && (
|
||||
<>
|
||||
Transaction hash:{' '}
|
||||
<Link
|
||||
as="a"
|
||||
href={`https://${process.env.NEXT_PUBLIC_EXPLORER_URL}/${txHash}`}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
{txHash}
|
||||
</Link>
|
||||
</>
|
||||
)}
|
||||
</>
|
||||
)
|
||||
if (submitRes.engine_result === 'tesSUCCESS') {
|
||||
state.deployLogs.push({
|
||||
type: 'success',
|
||||
message: 'Hook deployed successfully ✅'
|
||||
})
|
||||
|
||||
const txHash = submitRes.tx_json?.hash
|
||||
const resultMsg = ref(
|
||||
<>
|
||||
[<ResultLink result={submitRes.engine_result} />] {submitRes.engine_result_message}{' '}
|
||||
{txHash && (
|
||||
<>
|
||||
Transaction hash:{' '}
|
||||
<Link
|
||||
as="a"
|
||||
href={`https://${process.env.NEXT_PUBLIC_EXPLORER_URL}/${txHash}`}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
{txHash}
|
||||
</Link>
|
||||
</>
|
||||
)}
|
||||
</>
|
||||
)
|
||||
if (submitRes.engine_result === 'tesSUCCESS') {
|
||||
state.deployLogs.push({
|
||||
type: 'success',
|
||||
message: 'Hook deployed successfully ✅'
|
||||
})
|
||||
state.deployLogs.push({
|
||||
type: 'success',
|
||||
message: resultMsg
|
||||
})
|
||||
} else if (submitRes.engine_result) {
|
||||
state.deployLogs.push({
|
||||
type: 'error',
|
||||
message: resultMsg
|
||||
})
|
||||
} else {
|
||||
state.deployLogs.push({
|
||||
type: 'error',
|
||||
message: `[${submitRes.error}] ${submitRes.error_exception}`
|
||||
})
|
||||
}
|
||||
} catch (err) {
|
||||
console.log(err)
|
||||
state.deployLogs.push({
|
||||
type: 'success',
|
||||
message: resultMsg
|
||||
})
|
||||
} else if (submitRes.engine_result) {
|
||||
state.deployLogs.push({
|
||||
type: 'error',
|
||||
message: 'Error occurred while deploying'
|
||||
message: resultMsg
|
||||
})
|
||||
} else {
|
||||
state.deployLogs.push({
|
||||
type: 'error',
|
||||
message: `[${submitRes.error}] ${submitRes.error_exception}`
|
||||
})
|
||||
}
|
||||
if (currentAccount) {
|
||||
currentAccount.isLoading = false
|
||||
}
|
||||
return submitRes
|
||||
} catch (err) {
|
||||
console.error(err)
|
||||
state.deployLogs.push({
|
||||
type: 'error',
|
||||
message: 'Error occurred while deploying'
|
||||
})
|
||||
}
|
||||
if (currentAccount) {
|
||||
currentAccount.isLoading = false
|
||||
}
|
||||
return submitRes
|
||||
}
|
||||
|
||||
export const deleteHook = async (account: IAccount & { name?: string }) => {
|
||||
if (!state.client) {
|
||||
return
|
||||
}
|
||||
const currentAccount = state.accounts.find(acc => acc.address === account.address)
|
||||
if (currentAccount?.isLoading || !currentAccount?.hooks.length) {
|
||||
return
|
||||
}
|
||||
if (typeof window !== 'undefined') {
|
||||
const tx = {
|
||||
Account: account.address,
|
||||
TransactionType: 'SetHook',
|
||||
Sequence: account.sequence,
|
||||
Fee: '100000',
|
||||
Hooks: [
|
||||
{
|
||||
Hook: {
|
||||
CreateCode: '',
|
||||
Flags: 1
|
||||
}
|
||||
const tx = {
|
||||
Account: account.address,
|
||||
TransactionType: 'SetHook',
|
||||
Sequence: account.sequence,
|
||||
Fee: '100000',
|
||||
NetworkID: process.env.NEXT_PUBLIC_NETWORK_ID,
|
||||
Hooks: [
|
||||
{
|
||||
Hook: {
|
||||
CreateCode: '',
|
||||
Flags: 1
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
const keypair = derive.familySeed(account.secret)
|
||||
try {
|
||||
// Update tx Fee value with network estimation
|
||||
const res = await estimateFee(tx, account)
|
||||
tx['Fee'] = res?.base_fee ? res?.base_fee : '1000'
|
||||
} catch (err) {
|
||||
// use default value what you defined earlier
|
||||
console.log(err)
|
||||
}
|
||||
const { signedTransaction } = sign(tx, keypair)
|
||||
|
||||
if (currentAccount) {
|
||||
currentAccount.isLoading = true
|
||||
}
|
||||
let submitRes
|
||||
const toastId = toast.loading('Deleting hook...')
|
||||
try {
|
||||
submitRes = await state.client.send({
|
||||
command: 'submit',
|
||||
tx_blob: signedTransaction
|
||||
})
|
||||
|
||||
if (submitRes.engine_result === 'tesSUCCESS') {
|
||||
toast.success('Hook deleted successfully ✅', { id: toastId })
|
||||
state.deployLogs.push({
|
||||
type: 'success',
|
||||
message: 'Hook deleted successfully ✅'
|
||||
})
|
||||
state.deployLogs.push({
|
||||
type: 'success',
|
||||
message: `[${submitRes.engine_result}] ${submitRes.engine_result_message} Validated ledger index: ${submitRes.validated_ledger_index}`
|
||||
})
|
||||
currentAccount.hooks = []
|
||||
} else {
|
||||
toast.error(`${submitRes.engine_result_message || submitRes.error_exception}`, {
|
||||
id: toastId
|
||||
})
|
||||
state.deployLogs.push({
|
||||
type: 'error',
|
||||
message: `[${submitRes.engine_result || submitRes.error}] ${
|
||||
submitRes.engine_result_message || submitRes.error_exception
|
||||
}`
|
||||
})
|
||||
}
|
||||
} catch (err) {
|
||||
console.log(err)
|
||||
toast.error('Error occurred while deleting hook', { id: toastId })
|
||||
]
|
||||
}
|
||||
const keypair = derive.familySeed(account.secret)
|
||||
try {
|
||||
// Update tx Fee value with network estimation
|
||||
const res = await estimateFee(tx, account)
|
||||
tx['Fee'] = res?.base_fee || '1000'
|
||||
} catch (err) {
|
||||
console.error(err)
|
||||
}
|
||||
const { signedTransaction } = sign(tx, keypair)
|
||||
if (currentAccount) {
|
||||
currentAccount.isLoading = true
|
||||
}
|
||||
let submitRes
|
||||
const toastId = toast.loading('Deleting hook...')
|
||||
try {
|
||||
submitRes = await xrplSend({
|
||||
command: 'submit',
|
||||
tx_blob: signedTransaction
|
||||
})
|
||||
|
||||
if (submitRes.engine_result === 'tesSUCCESS') {
|
||||
toast.success('Hook deleted successfully ✅', { id: toastId })
|
||||
state.deployLogs.push({
|
||||
type: 'success',
|
||||
message: 'Hook deleted successfully ✅'
|
||||
})
|
||||
state.deployLogs.push({
|
||||
type: 'success',
|
||||
message: `[${submitRes.engine_result}] ${submitRes.engine_result_message} Validated ledger index: ${submitRes.validated_ledger_index}`
|
||||
})
|
||||
currentAccount.hooks = []
|
||||
} else {
|
||||
toast.error(`${submitRes.engine_result_message || submitRes.error_exception}`, {
|
||||
id: toastId
|
||||
})
|
||||
state.deployLogs.push({
|
||||
type: 'error',
|
||||
message: 'Error occurred while deleting hook'
|
||||
message: `[${submitRes.engine_result || submitRes.error}] ${
|
||||
submitRes.engine_result_message || submitRes.error_exception
|
||||
}`
|
||||
})
|
||||
}
|
||||
if (currentAccount) {
|
||||
currentAccount.isLoading = false
|
||||
}
|
||||
return submitRes
|
||||
} catch (err) {
|
||||
console.log(err)
|
||||
toast.error('Error occurred while deleting hook', { id: toastId })
|
||||
state.deployLogs.push({
|
||||
type: 'error',
|
||||
message: 'Error occurred while deleting hook'
|
||||
})
|
||||
}
|
||||
if (currentAccount) {
|
||||
currentAccount.isLoading = false
|
||||
}
|
||||
return submitRes
|
||||
}
|
||||
|
||||
@@ -1,66 +0,0 @@
|
||||
import { derive, sign } from 'xrpl-accountlib'
|
||||
|
||||
import state from '..'
|
||||
import type { IAccount } from '..'
|
||||
|
||||
interface TransactionOptions {
|
||||
TransactionType: string
|
||||
Account?: string
|
||||
Fee?: string
|
||||
Destination?: string
|
||||
[index: string]: any
|
||||
}
|
||||
interface OtherOptions {
|
||||
logPrefix?: string
|
||||
}
|
||||
|
||||
export const sendTransaction = async (
|
||||
account: IAccount,
|
||||
txOptions: TransactionOptions,
|
||||
options?: OtherOptions
|
||||
) => {
|
||||
if (!state.client) throw Error('XRPL client not initalized')
|
||||
|
||||
const { Fee = '1000', ...opts } = txOptions
|
||||
const tx: TransactionOptions = {
|
||||
Account: account.address,
|
||||
Sequence: account.sequence,
|
||||
Fee, // TODO auto-fillable default
|
||||
...opts
|
||||
}
|
||||
const { logPrefix = '' } = options || {}
|
||||
try {
|
||||
const signedAccount = derive.familySeed(account.secret)
|
||||
const { signedTransaction } = sign(tx, signedAccount)
|
||||
const response = await state.client.send({
|
||||
command: 'submit',
|
||||
tx_blob: signedTransaction
|
||||
})
|
||||
if (response.engine_result === 'tesSUCCESS') {
|
||||
state.transactionLogs.push({
|
||||
type: 'success',
|
||||
message: `${logPrefix}[${response.engine_result}] ${response.engine_result_message}`
|
||||
})
|
||||
} else {
|
||||
state.transactionLogs.push({
|
||||
type: 'error',
|
||||
message: `${logPrefix}[${response.error || response.engine_result}] ${
|
||||
response.error_exception || response.engine_result_message
|
||||
}`
|
||||
})
|
||||
}
|
||||
const currAcc = state.accounts.find(acc => acc.address === account.address)
|
||||
if (currAcc && response.account_sequence_next) {
|
||||
currAcc.sequence = response.account_sequence_next
|
||||
}
|
||||
} catch (err) {
|
||||
console.error(err)
|
||||
state.transactionLogs.push({
|
||||
type: 'error',
|
||||
message:
|
||||
err instanceof Error
|
||||
? `${logPrefix}Error: ${err.message}`
|
||||
: `${logPrefix}Something went wrong, try again later`
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -4,6 +4,7 @@ import state from '..'
|
||||
import type { IAccount } from '..'
|
||||
import ResultLink from '../../components/ResultLink'
|
||||
import { ref } from 'valtio'
|
||||
import { xrplSend } from './xrpl-client'
|
||||
|
||||
interface TransactionOptions {
|
||||
TransactionType: string
|
||||
@@ -21,20 +22,23 @@ export const sendTransaction = async (
|
||||
txOptions: TransactionOptions,
|
||||
options?: OtherOptions
|
||||
) => {
|
||||
if (!state.client) throw Error('XRPL client not initalized')
|
||||
|
||||
const { Fee = '1000', ...opts } = txOptions
|
||||
const tx: TransactionOptions = {
|
||||
Account: account.address,
|
||||
Sequence: account.sequence,
|
||||
Fee, // TODO auto-fillable default
|
||||
Fee,
|
||||
NetworkID: process.env.NEXT_PUBLIC_NETWORK_ID,
|
||||
...opts
|
||||
}
|
||||
const { logPrefix = '' } = options || {}
|
||||
state.transactionLogs.push({
|
||||
type: 'log',
|
||||
message: `${logPrefix}${JSON.stringify(tx, null, 2)}`
|
||||
})
|
||||
try {
|
||||
const signedAccount = derive.familySeed(account.secret)
|
||||
const { signedTransaction } = sign(tx, signedAccount)
|
||||
const response = await state.client.send({
|
||||
const response = await xrplSend({
|
||||
command: 'submit',
|
||||
tx_blob: signedTransaction
|
||||
})
|
||||
|
||||
7
state/actions/xrpl-client.ts
Normal file
7
state/actions/xrpl-client.ts
Normal file
@@ -0,0 +1,7 @@
|
||||
import { XrplClient } from 'xrpl-client';
|
||||
import state from '..';
|
||||
|
||||
export const xrplSend = async(...params: Parameters<XrplClient['send']>) => {
|
||||
const client = await state.client.ready()
|
||||
return client.send(...params);
|
||||
}
|
||||
@@ -78,7 +78,7 @@ export interface IState {
|
||||
splits: {
|
||||
[id: string]: SplitSize
|
||||
}
|
||||
client: XrplClient | null
|
||||
client: XrplClient
|
||||
clientStatus: 'offline' | 'online'
|
||||
mainModalOpen: boolean
|
||||
mainModalShowed: boolean
|
||||
@@ -113,7 +113,7 @@ let initialState: IState = {
|
||||
tabSize: 2
|
||||
},
|
||||
splits: {},
|
||||
client: null,
|
||||
client: undefined!, // set below only.
|
||||
clientStatus: 'offline' as 'offline',
|
||||
mainModalOpen: false,
|
||||
mainModalShowed: false,
|
||||
@@ -153,9 +153,9 @@ const state = proxy<IState>({
|
||||
})
|
||||
// Initialize socket connection
|
||||
const client = new XrplClient(`wss://${process.env.NEXT_PUBLIC_TESTNET_URL}`)
|
||||
state.client = ref(client);
|
||||
|
||||
client.on('online', () => {
|
||||
state.client = ref(client)
|
||||
state.clientStatus = 'online'
|
||||
})
|
||||
|
||||
|
||||
@@ -5,28 +5,46 @@ 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]>,
|
||||
'Account' | 'Sequence' | 'TransactionType'
|
||||
typeof commonFields[number]
|
||||
>
|
||||
|
||||
export const defaultTransaction: TransactionState = {
|
||||
@@ -34,6 +52,9 @@ export const defaultTransaction: TransactionState = {
|
||||
selectedAccount: null,
|
||||
selectedDestAccount: null,
|
||||
selectedFlags: null,
|
||||
hookParameters: {},
|
||||
memos: {},
|
||||
editorIsSaved: true,
|
||||
txIsLoading: false,
|
||||
txIsDisabled: false,
|
||||
txFields: {},
|
||||
@@ -158,7 +179,7 @@ export const prepareState = (value: string, transactionType?: string) => {
|
||||
return
|
||||
}
|
||||
|
||||
const { Account, TransactionType, Destination, ...rest } = options
|
||||
const { Account, TransactionType, Destination, HookParameters, Memos, ...rest } = options
|
||||
let tx: Partial<TransactionState> = {}
|
||||
const schema = getTxFields(transactionType)
|
||||
|
||||
@@ -188,6 +209,22 @@ export const prepareState = (value: string, transactionType?: string) => {
|
||||
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) {
|
||||
@@ -236,6 +273,7 @@ export const prepareState = (value: string, transactionType?: string) => {
|
||||
})
|
||||
|
||||
tx.txFields = rest
|
||||
tx.editorIsSaved = true;
|
||||
|
||||
return tx
|
||||
}
|
||||
@@ -246,12 +284,12 @@ export const getTxFields = (tt?: string) => {
|
||||
if (!txFields) return {}
|
||||
|
||||
let _txFields = Object.keys(txFields)
|
||||
.filter(key => !['TransactionType', 'Account', 'Sequence'].includes(key))
|
||||
.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 }
|
||||
export { transactionsData, commonFields }
|
||||
|
||||
export const transactionsOptions = transactionsData.map(tx => ({
|
||||
value: tx.TransactionType,
|
||||
|
||||
31
utils/accountInfo.ts
Normal file
31
utils/accountInfo.ts
Normal file
@@ -0,0 +1,31 @@
|
||||
import toast from 'react-hot-toast'
|
||||
|
||||
import { xrplSend } from '../state/actions/xrpl-client'
|
||||
|
||||
interface AccountInfo {
|
||||
Account: string,
|
||||
Sequence: number,
|
||||
Flags: number,
|
||||
Balance?: string,
|
||||
}
|
||||
|
||||
const fetchAccountInfo = async (
|
||||
address: string,
|
||||
opts: { silent?: boolean } = {}
|
||||
): Promise<AccountInfo | undefined> => {
|
||||
try {
|
||||
const res = await xrplSend({
|
||||
id: `hooks-builder-req-info-${address}`,
|
||||
command: 'account_info',
|
||||
account: address
|
||||
})
|
||||
return res.account_data;
|
||||
} catch (err) {
|
||||
if (!opts.silent) {
|
||||
console.error(err)
|
||||
toast.error('Could not fetch account info!')
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export default fetchAccountInfo
|
||||
@@ -1,6 +1,7 @@
|
||||
import toast from 'react-hot-toast'
|
||||
import { derive, sign } from 'xrpl-accountlib'
|
||||
import state, { IAccount } from '../state'
|
||||
import { IAccount } from '../state'
|
||||
import { xrplSend } from '../state/actions/xrpl-client'
|
||||
|
||||
const estimateFee = async (
|
||||
tx: Record<string, unknown>,
|
||||
@@ -22,7 +23,7 @@ const estimateFee = async (
|
||||
const keypair = derive.familySeed(account.secret)
|
||||
const { signedTransaction } = sign(copyTx, keypair)
|
||||
|
||||
const res = await state.client?.send({ command: 'fee', tx_blob: signedTransaction })
|
||||
const res = await xrplSend({ command: 'fee', tx_blob: signedTransaction })
|
||||
if (res && res.drops) {
|
||||
return res.drops
|
||||
}
|
||||
|
||||
@@ -23,23 +23,22 @@ export const tts = {
|
||||
ttNFTOKEN_BURN: 26,
|
||||
ttNFTOKEN_CREATE_OFFER: 27,
|
||||
ttNFTOKEN_CANCEL_OFFER: 28,
|
||||
ttNFTOKEN_ACCEPT_OFFER: 29
|
||||
ttNFTOKEN_ACCEPT_OFFER: 29,
|
||||
ttINVOKE: 99,
|
||||
}
|
||||
|
||||
export type TTS = typeof tts
|
||||
|
||||
const calculateHookOn = (arr: (keyof TTS)[]) => {
|
||||
let start = '0x000000003e3ff5bf'
|
||||
let s = '0x3e3ff5bf'
|
||||
arr.forEach(n => {
|
||||
let v = BigInt(start)
|
||||
v ^= BigInt(1) << BigInt(tts[n as keyof TTS])
|
||||
let s = v.toString(16)
|
||||
let l = s.length
|
||||
if (l < 16) s = '0'.repeat(16 - l) + s
|
||||
s = '0x' + s
|
||||
start = s
|
||||
let v = BigInt(s)
|
||||
v ^= BigInt(1) << BigInt(tts[n])
|
||||
s = "0x" + v.toString(16)
|
||||
})
|
||||
return start.substring(2)
|
||||
s = s.replace('0x', '')
|
||||
s = s.padStart(64, '0')
|
||||
return s
|
||||
}
|
||||
|
||||
export default calculateHookOn
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -20,6 +20,13 @@ export type SetHookData = {
|
||||
}
|
||||
$metaData?: any
|
||||
}[]
|
||||
Memos?: {
|
||||
Memo: {
|
||||
MemoType: string,
|
||||
MemoData: string
|
||||
MemoFormat: string
|
||||
}
|
||||
}[]
|
||||
// HookGrants: {
|
||||
// HookGrant: {
|
||||
// Authorize: string;
|
||||
@@ -74,3 +81,19 @@ export const getInvokeOptions = (content?: string) => {
|
||||
|
||||
return invokeOptions
|
||||
}
|
||||
|
||||
export function toHex(str: string) {
|
||||
var result = ''
|
||||
for (var i = 0; i < str.length; i++) {
|
||||
result += str.charCodeAt(i).toString(16)
|
||||
}
|
||||
return result.toUpperCase()
|
||||
}
|
||||
|
||||
export function fromHex(hex: string) {
|
||||
var str = ''
|
||||
for (var i = 0; i < hex.length; i += 2) {
|
||||
str += String.fromCharCode(parseInt(hex.substring(i, i + 2), 16))
|
||||
}
|
||||
return str
|
||||
}
|
||||
Reference in New Issue
Block a user