Compare commits
36 Commits
feat/asc-c
...
fix/params
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
423ee18e6a | ||
|
|
3bb26d0c9b | ||
|
|
43c83d0de6 | ||
|
|
6bb407cb0f | ||
|
|
d7b29ba809 | ||
|
|
ca81d8ad41 | ||
|
|
a7e59d7b73 | ||
|
|
8f6b28cef5 | ||
|
|
c1815f272b | ||
|
|
76871a8041 | ||
|
|
d2033c8035 | ||
|
|
48daf1c5c8 | ||
|
|
a3365e4beb | ||
|
|
45d813cdad | ||
|
|
911416aa2f | ||
|
|
2d836af9ed | ||
|
|
4f0fc838be | ||
|
|
fa93912c38 | ||
|
|
f5cb76c302 | ||
|
|
df1d65dcab | ||
|
|
1513f78991 | ||
|
|
3a064f307b | ||
|
|
6fca05f310 | ||
|
|
31e67d382f | ||
|
|
27475301e4 | ||
|
|
934283976a | ||
|
|
2d9ca2674e | ||
|
|
221c727af6 | ||
|
|
3b0a8c44c9 | ||
|
|
0d9e9e7b45 | ||
|
|
094f739b80 | ||
|
|
2c2bf59bcd | ||
|
|
2d82966b3b | ||
|
|
93c5ef231e | ||
|
|
53c2104b94 | ||
|
|
c336ff8334 |
@@ -5,7 +5,8 @@ GITHUB_ID=""
|
|||||||
NEXT_PUBLIC_COMPILE_API_ENDPOINT="http://localhost:9000/api/build"
|
NEXT_PUBLIC_COMPILE_API_ENDPOINT="http://localhost:9000/api/build"
|
||||||
NEXT_PUBLIC_COMPILE_API_BASE_URL="http://localhost:9000"
|
NEXT_PUBLIC_COMPILE_API_BASE_URL="http://localhost:9000"
|
||||||
NEXT_PUBLIC_LANGUAGE_SERVER_API_ENDPOINT="ws://localhost:9000/language-server/c"
|
NEXT_PUBLIC_LANGUAGE_SERVER_API_ENDPOINT="ws://localhost:9000/language-server/c"
|
||||||
NEXT_PUBLIC_TESTNET_URL="hooks-testnet-v2.xrpl-labs.com"
|
NEXT_PUBLIC_TESTNET_URL="hooks-testnet-v3.xrpl-labs.com"
|
||||||
NEXT_PUBLIC_DEBUG_STREAM_URL="hooks-testnet-v2-debugstream.xrpl-labs.com"
|
NEXT_PUBLIC_DEBUG_STREAM_URL="hooks-testnet-v3-debugstream.xrpl-labs.com"
|
||||||
NEXT_PUBLIC_EXPLORER_URL="hooks-testnet-v2-explorer.xrpl-labs.com"
|
NEXT_PUBLIC_EXPLORER_URL="hooks-testnet-v3-explorer.xrpl-labs.com"
|
||||||
NEXT_PUBLIC_SITE_URL=http://localhost:3000
|
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
|
||||||
.vercel
|
.vercel
|
||||||
.vscode
|
.vscode
|
||||||
|
|
||||||
|
# yarn
|
||||||
|
.yarnrc.yml
|
||||||
|
.yarn/
|
||||||
|
|||||||
@@ -33,6 +33,7 @@ import { addFunds } from '../state/actions/addFaucetAccount'
|
|||||||
import { deleteHook } from '../state/actions/deployHook'
|
import { deleteHook } from '../state/actions/deployHook'
|
||||||
import { capitalize } from '../utils/helpers'
|
import { capitalize } from '../utils/helpers'
|
||||||
import { deleteAccount } from '../state/actions/deleteAccount'
|
import { deleteAccount } from '../state/actions/deleteAccount'
|
||||||
|
import { xrplSend } from '../state/actions/xrpl-client'
|
||||||
|
|
||||||
export const AccountDialog = ({
|
export const AccountDialog = ({
|
||||||
activeAccountAddress,
|
activeAccountAddress,
|
||||||
@@ -301,7 +302,7 @@ const Accounts: FC<AccountProps> = props => {
|
|||||||
const fetchAccInfo = async () => {
|
const fetchAccInfo = async () => {
|
||||||
if (snap.clientStatus === 'online') {
|
if (snap.clientStatus === 'online') {
|
||||||
const requests = snap.accounts.map(acc =>
|
const requests = snap.accounts.map(acc =>
|
||||||
snap.client?.send({
|
xrplSend({
|
||||||
id: `hooks-builder-req-info-${acc.address}`,
|
id: `hooks-builder-req-info-${acc.address}`,
|
||||||
command: 'account_info',
|
command: 'account_info',
|
||||||
account: acc.address
|
account: acc.address
|
||||||
@@ -329,7 +330,7 @@ const Accounts: FC<AccountProps> = props => {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
const objectRequests = snap.accounts.map(acc => {
|
const objectRequests = snap.accounts.map(acc => {
|
||||||
return snap.client?.send({
|
return xrplSend({
|
||||||
id: `hooks-builder-req-objects-${acc.address}`,
|
id: `hooks-builder-req-objects-${acc.address}`,
|
||||||
command: 'account_objects',
|
command: 'account_objects',
|
||||||
account: acc.address
|
account: acc.address
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import { subscribeKey } from 'valtio/utils'
|
|||||||
import { Select } from '.'
|
import { Select } from '.'
|
||||||
import state, { ILog, transactionsState } from '../state'
|
import state, { ILog, transactionsState } from '../state'
|
||||||
import { extractJSON } from '../utils/json'
|
import { extractJSON } from '../utils/json'
|
||||||
|
import EnrichLog from './EnrichLog'
|
||||||
import LogBox from './LogBox'
|
import LogBox from './LogBox'
|
||||||
|
|
||||||
interface ISelect<T = string> {
|
interface ISelect<T = string> {
|
||||||
@@ -99,6 +100,11 @@ const addListeners = (account: ISelect | null) => {
|
|||||||
|
|
||||||
subscribeKey(streamState, 'selectedAccount', addListeners)
|
subscribeKey(streamState, 'selectedAccount', addListeners)
|
||||||
|
|
||||||
|
const clearLog = () => {
|
||||||
|
streamState.logs = []
|
||||||
|
streamState.statusChangeTimestamp = Date.now()
|
||||||
|
}
|
||||||
|
|
||||||
const DebugStream = () => {
|
const DebugStream = () => {
|
||||||
const { selectedAccount, logs } = useSnapshot(streamState)
|
const { selectedAccount, logs } = useSnapshot(streamState)
|
||||||
const { activeHeader: activeTxTab } = useSnapshot(transactionsState)
|
const { activeHeader: activeTxTab } = useSnapshot(transactionsState)
|
||||||
@@ -134,11 +140,6 @@ const DebugStream = () => {
|
|||||||
streamState.selectedAccount = account
|
streamState.selectedAccount = account
|
||||||
}, [activeTxTab])
|
}, [activeTxTab])
|
||||||
|
|
||||||
const clearLog = () => {
|
|
||||||
streamState.logs = []
|
|
||||||
streamState.statusChangeTimestamp = Date.now()
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<LogBox enhanced renderNav={renderNav} title="Debug stream" logs={logs} clearLog={clearLog} />
|
<LogBox enhanced renderNav={renderNav} title="Debug stream" logs={logs} clearLog={clearLog} />
|
||||||
)
|
)
|
||||||
@@ -157,9 +158,11 @@ export const pushLog = (str: any, opts: Partial<Pick<ILog, 'type'>> = {}): ILog
|
|||||||
const timestring = !timestamp ? tm : new Date(timestamp).toLocaleTimeString()
|
const timestring = !timestamp ? tm : new Date(timestamp).toLocaleTimeString()
|
||||||
|
|
||||||
const extracted = extractJSON(msg)
|
const extracted = extractJSON(msg)
|
||||||
const message = !extracted ? msg : msg.slice(0, extracted.start) + msg.slice(extracted.end + 1)
|
const _message = !extracted ? msg : msg.slice(0, extracted.start) + msg.slice(extracted.end + 1)
|
||||||
|
const message = ref(<EnrichLog str={_message} />)
|
||||||
|
|
||||||
const jsonData = extracted ? JSON.stringify(extracted.result, null, 2) : undefined
|
const _jsonData = extracted ? JSON.stringify(extracted.result, null, 2) : undefined
|
||||||
|
const jsonData = _jsonData ? ref(<EnrichLog str={_jsonData} />) : undefined
|
||||||
|
|
||||||
if (extracted?.result?.id?._Request?.includes('hooks-builder-req')) {
|
if (extracted?.result?.id?._Request?.includes('hooks-builder-req')) {
|
||||||
return
|
return
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ const contentShow = keyframes({
|
|||||||
'100%': { opacity: 1 }
|
'100%': { opacity: 1 }
|
||||||
})
|
})
|
||||||
const StyledOverlay = styled(DialogPrimitive.Overlay, {
|
const StyledOverlay = styled(DialogPrimitive.Overlay, {
|
||||||
zIndex: 10000,
|
zIndex: 3000,
|
||||||
backgroundColor: blackA.blackA9,
|
backgroundColor: blackA.blackA9,
|
||||||
position: 'fixed',
|
position: 'fixed',
|
||||||
inset: 0,
|
inset: 0,
|
||||||
|
|||||||
73
components/EnrichLog.tsx
Normal file
73
components/EnrichLog.tsx
Normal file
@@ -0,0 +1,73 @@
|
|||||||
|
import { FC, useState } from 'react'
|
||||||
|
import regexifyString from 'regexify-string'
|
||||||
|
import { useSnapshot } from 'valtio'
|
||||||
|
import { Link } from '.'
|
||||||
|
import state from '../state'
|
||||||
|
import { AccountDialog } from './Accounts'
|
||||||
|
import Tooltip from './Tooltip'
|
||||||
|
import hookSetCodes from '../content/hook-set-codes.json'
|
||||||
|
import { capitalize } from '../utils/helpers'
|
||||||
|
|
||||||
|
interface EnrichLogProps {
|
||||||
|
str?: string
|
||||||
|
}
|
||||||
|
|
||||||
|
const EnrichLog: FC<EnrichLogProps> = ({ str }) => {
|
||||||
|
const { accounts } = useSnapshot(state)
|
||||||
|
const [dialogAccount, setDialogAccount] = useState<string | null>(null)
|
||||||
|
if (!str || !accounts.length) return <>{str}</>
|
||||||
|
|
||||||
|
const addrs = accounts.map(acc => acc.address)
|
||||||
|
const regex = `(${addrs.join('|')}|HookSet\\(\\d+\\))`
|
||||||
|
const res = regexifyString({
|
||||||
|
pattern: new RegExp(regex, 'gim'),
|
||||||
|
decorator: (match, idx) => {
|
||||||
|
if (match.startsWith('r')) {
|
||||||
|
// Account
|
||||||
|
const name = accounts.find(acc => acc.address === match)?.name
|
||||||
|
return (
|
||||||
|
<Link
|
||||||
|
key={match + idx}
|
||||||
|
as="a"
|
||||||
|
onClick={() => setDialogAccount(match)}
|
||||||
|
title={match}
|
||||||
|
highlighted
|
||||||
|
>
|
||||||
|
{name || match}
|
||||||
|
</Link>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
if (match.startsWith('HookSet')) {
|
||||||
|
const code = match.match(/^HookSet\((\d+)\)/)?.[1]
|
||||||
|
const val = hookSetCodes.find(v => code && v.code === +code)
|
||||||
|
console.log({ code, val })
|
||||||
|
if (!val) return match
|
||||||
|
|
||||||
|
const content = capitalize(val.description) || 'No hint available!'
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
HookSet(
|
||||||
|
<Tooltip content={content}>
|
||||||
|
<Link>{val.identifier}</Link>
|
||||||
|
</Tooltip>
|
||||||
|
)
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
return match
|
||||||
|
},
|
||||||
|
input: str
|
||||||
|
})
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
{res}
|
||||||
|
<AccountDialog
|
||||||
|
setActiveAccountAddress={setDialogAccount}
|
||||||
|
activeAccountAddress={dialogAccount}
|
||||||
|
/>
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default EnrichLog
|
||||||
@@ -200,15 +200,15 @@ const HooksEditor = () => {
|
|||||||
defaultValue={file?.content}
|
defaultValue={file?.content}
|
||||||
// onChange={val => (state.files[snap.active].content = val)} // Auto save?
|
// onChange={val => (state.files[snap.active].content = val)} // Auto save?
|
||||||
beforeMount={monaco => {
|
beforeMount={monaco => {
|
||||||
if (!snap.editorCtx) {
|
// if (!snap.editorCtx) {
|
||||||
snap.files.forEach(file =>
|
// snap.files.forEach(file =>
|
||||||
monaco.editor.createModel(
|
// monaco.editor.createModel(
|
||||||
file.content,
|
// file.content,
|
||||||
file.language,
|
// file.language,
|
||||||
monaco.Uri.parse(`file:///work/c/${file.name}`)
|
// monaco.Uri.parse(`file:///work/c/${file.name}`)
|
||||||
)
|
// )
|
||||||
)
|
// )
|
||||||
}
|
// }
|
||||||
|
|
||||||
// create the web socket
|
// create the web socket
|
||||||
if (!subscriptionRef.current) {
|
if (!subscriptionRef.current) {
|
||||||
@@ -218,6 +218,11 @@ const HooksEditor = () => {
|
|||||||
aliases: ['C', 'c', 'H', 'h'],
|
aliases: ['C', 'c', 'H', 'h'],
|
||||||
mimetypes: ['text/plain']
|
mimetypes: ['text/plain']
|
||||||
})
|
})
|
||||||
|
monaco.languages.register({
|
||||||
|
id: 'text',
|
||||||
|
extensions: ['.txt'],
|
||||||
|
mimetypes: ['text/plain'],
|
||||||
|
})
|
||||||
MonacoServices.install(monaco)
|
MonacoServices.install(monaco)
|
||||||
const webSocket = createWebSocket(
|
const webSocket = createWebSocket(
|
||||||
process.env.NEXT_PUBLIC_LANGUAGE_SERVER_API_ENDPOINT || ''
|
process.env.NEXT_PUBLIC_LANGUAGE_SERVER_API_ENDPOINT || ''
|
||||||
|
|||||||
@@ -1,15 +1,12 @@
|
|||||||
import { useRef, useLayoutEffect, ReactNode, FC, useState, useCallback } from 'react'
|
import { useRef, useLayoutEffect, ReactNode, FC, useState } from 'react'
|
||||||
import { IconProps, Notepad, Prohibit } from 'phosphor-react'
|
import { IconProps, Notepad, Prohibit } from 'phosphor-react'
|
||||||
import useStayScrolled from 'react-stay-scrolled'
|
import useStayScrolled from 'react-stay-scrolled'
|
||||||
import NextLink from 'next/link'
|
import NextLink from 'next/link'
|
||||||
|
|
||||||
import Container from './Container'
|
import Container from './Container'
|
||||||
import LogText from './LogText'
|
import LogText from './LogText'
|
||||||
import state, { ILog } from '../state'
|
import { ILog } from '../state'
|
||||||
import { Pre, Link, Heading, Button, Text, Flex, Box } from '.'
|
import { Pre, Link, Heading, Button, Text, Flex, Box } from '.'
|
||||||
import regexifyString from 'regexify-string'
|
|
||||||
import { useSnapshot } from 'valtio'
|
|
||||||
import { AccountDialog } from './Accounts'
|
|
||||||
|
|
||||||
interface ILogBox {
|
interface ILogBox {
|
||||||
title: string
|
title: string
|
||||||
@@ -143,70 +140,25 @@ const LogBox: FC<ILogBox> = ({
|
|||||||
export const Log: FC<ILog> = ({
|
export const Log: FC<ILog> = ({
|
||||||
type,
|
type,
|
||||||
timestring,
|
timestring,
|
||||||
message: _message,
|
message,
|
||||||
link,
|
link,
|
||||||
linkText,
|
linkText,
|
||||||
defaultCollapsed,
|
defaultCollapsed,
|
||||||
jsonData: _jsonData
|
jsonData
|
||||||
}) => {
|
}) => {
|
||||||
const [expanded, setExpanded] = useState(!defaultCollapsed)
|
const [expanded, setExpanded] = useState(!defaultCollapsed)
|
||||||
const { accounts } = useSnapshot(state)
|
|
||||||
const [dialogAccount, setDialogAccount] = useState<string | null>(null)
|
|
||||||
|
|
||||||
const enrichAccounts = useCallback(
|
|
||||||
(str?: string): ReactNode => {
|
|
||||||
if (!str || !accounts.length) return str
|
|
||||||
|
|
||||||
const pattern = `(${accounts.map(acc => acc.address).join('|')})`
|
|
||||||
const res = regexifyString({
|
|
||||||
pattern: new RegExp(pattern, 'gim'),
|
|
||||||
decorator: (match, idx) => {
|
|
||||||
const name = accounts.find(acc => acc.address === match)?.name
|
|
||||||
return (
|
|
||||||
<Link
|
|
||||||
key={match + idx}
|
|
||||||
as="a"
|
|
||||||
onClick={() => setDialogAccount(match)}
|
|
||||||
title={match}
|
|
||||||
highlighted
|
|
||||||
>
|
|
||||||
{name || match}
|
|
||||||
</Link>
|
|
||||||
)
|
|
||||||
},
|
|
||||||
input: str
|
|
||||||
})
|
|
||||||
|
|
||||||
return <>{res}</>
|
|
||||||
},
|
|
||||||
[accounts]
|
|
||||||
)
|
|
||||||
|
|
||||||
let message: ReactNode
|
|
||||||
|
|
||||||
if (typeof _message === 'string') {
|
|
||||||
_message = _message.trim().replace(/\n /gi, '\n')
|
|
||||||
if (_message) message = enrichAccounts(_message)
|
|
||||||
else message = <Text muted>{'""'}</Text>
|
|
||||||
} else {
|
|
||||||
message = _message
|
|
||||||
}
|
|
||||||
|
|
||||||
const jsonData = enrichAccounts(_jsonData)
|
|
||||||
|
|
||||||
|
if (message === undefined) message = <Text muted>{'undefined'}</Text>
|
||||||
|
else if (message === '') message = <Text muted>{'""'}</Text>
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<AccountDialog
|
|
||||||
setActiveAccountAddress={setDialogAccount}
|
|
||||||
activeAccountAddress={dialogAccount}
|
|
||||||
/>
|
|
||||||
<LogText variant={type}>
|
<LogText variant={type}>
|
||||||
{timestring && (
|
{timestring && (
|
||||||
<Text muted monospace>
|
<Text muted monospace>
|
||||||
{timestring}{' '}
|
{timestring}{' '}
|
||||||
</Text>
|
</Text>
|
||||||
)}
|
)}
|
||||||
<Pre>{message} </Pre>
|
<Pre>{message}</Pre>
|
||||||
{link && (
|
{link && (
|
||||||
<NextLink href={link} shallow passHref>
|
<NextLink href={link} shallow passHref>
|
||||||
<Link as="a">{linkText}</Link>
|
<Link as="a">{linkText}</Link>
|
||||||
@@ -219,7 +171,6 @@ export const Log: FC<ILog> = ({
|
|||||||
)}
|
)}
|
||||||
{expanded && jsonData && <Pre block>{jsonData}</Pre>}
|
{expanded && jsonData && <Pre block>{jsonData}</Pre>}
|
||||||
</LogText>
|
</LogText>
|
||||||
<br />
|
|
||||||
</>
|
</>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
24
components/ResultLink.tsx
Normal file
24
components/ResultLink.tsx
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
import { FC } from 'react'
|
||||||
|
import { Link } from '.'
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
result?: string
|
||||||
|
}
|
||||||
|
|
||||||
|
const ResultLink: FC<Props> = ({ result }) => {
|
||||||
|
if (!result) return null
|
||||||
|
let href: string
|
||||||
|
if (result === 'tesSUCCESS') {
|
||||||
|
href = 'https://xrpl.org/tes-success.html'
|
||||||
|
} else {
|
||||||
|
// Going shortcut here because of url structure, if that changes we will do it manually
|
||||||
|
href = `https://xrpl.org/${result.slice(0, 3)}-codes.html`
|
||||||
|
}
|
||||||
|
return (
|
||||||
|
<Link as="a" href={href} target="_blank" rel="noopener noreferrer">
|
||||||
|
{result}
|
||||||
|
</Link>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default ResultLink
|
||||||
@@ -21,7 +21,7 @@ import { saveFile } from '../../state/actions/saveFile'
|
|||||||
import { getErrors, getTags } from '../../utils/comment-parser'
|
import { getErrors, getTags } from '../../utils/comment-parser'
|
||||||
import toast from 'react-hot-toast'
|
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
|
let processString: string | undefined
|
||||||
const process = { env: { NODE_ENV: 'production' } } as any
|
const process = { env: { NODE_ENV: 'production' } } as any
|
||||||
if (data) {
|
if (data) {
|
||||||
@@ -29,8 +29,10 @@ const generateHtmlTemplate = (code: string, data?: Record<string, any>) => {
|
|||||||
process.env[key] = data[key]
|
process.env[key] = data[key]
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
processString = JSON.stringify(process)
|
processString = JSON.stringify(process)
|
||||||
|
|
||||||
|
const libs = (await import("xrpl-accountlib/dist/browser.hook-bundle.js")).default;
|
||||||
return `
|
return `
|
||||||
<html>
|
<html>
|
||||||
<head>
|
<head>
|
||||||
@@ -72,7 +74,9 @@ const generateHtmlTemplate = (code: string, data?: Record<string, any>) => {
|
|||||||
|
|
||||||
window.addEventListener('error', windowErrorHandler);
|
window.addEventListener('error', windowErrorHandler);
|
||||||
</script>
|
</script>
|
||||||
|
<script>
|
||||||
|
${libs}
|
||||||
|
</script>
|
||||||
<script type="module">
|
<script type="module">
|
||||||
${code}
|
${code}
|
||||||
</script>
|
</script>
|
||||||
@@ -100,6 +104,7 @@ const RunScript: React.FC<{ file: IFile }> = ({ file: { content, name } }) => {
|
|||||||
const [fields, setFields] = useState<Fields>({})
|
const [fields, setFields] = useState<Fields>({})
|
||||||
const [iFrameCode, setIframeCode] = useState('')
|
const [iFrameCode, setIframeCode] = useState('')
|
||||||
const [isDialogOpen, setIsDialogOpen] = useState(false)
|
const [isDialogOpen, setIsDialogOpen] = useState(false)
|
||||||
|
const [isLoading, setIsLoading] = useState(false)
|
||||||
|
|
||||||
const getFields = useCallback(() => {
|
const getFields = useCallback(() => {
|
||||||
const inputTags = ['input', 'param', 'arg', 'argument']
|
const inputTags = ['input', 'param', 'arg', 'argument']
|
||||||
@@ -127,16 +132,31 @@ const RunScript: React.FC<{ file: IFile }> = ({ file: { content, name } }) => {
|
|||||||
return fields
|
return fields
|
||||||
}, [content])
|
}, [content])
|
||||||
|
|
||||||
const runScript = useCallback(() => {
|
const runScript = useCallback(async () => {
|
||||||
|
setIsLoading(true);
|
||||||
try {
|
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 = {}
|
let data: any = {}
|
||||||
Object.keys(fields).forEach(key => {
|
Object.keys(fields).forEach(key => {
|
||||||
data[key] = fields[key].value
|
data[key] = fields[key].value
|
||||||
})
|
})
|
||||||
const template = generateHtmlTemplate(content, data)
|
const template = await generateHtmlTemplate(content, data)
|
||||||
|
|
||||||
setIframeCode(template)
|
setIframeCode(template)
|
||||||
|
|
||||||
|
loaded = true
|
||||||
|
if (toastId) {
|
||||||
|
toast.dismiss(toastId)
|
||||||
|
}
|
||||||
state.scriptLogs = [{ type: 'success', message: 'Started running...' }]
|
state.scriptLogs = [{ type: 'success', message: 'Started running...' }]
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
state.scriptLogs = [
|
state.scriptLogs = [
|
||||||
@@ -145,6 +165,7 @@ const RunScript: React.FC<{ file: IFile }> = ({ file: { content, name } }) => {
|
|||||||
{ type: 'error', message: err?.message || 'Could not parse template' }
|
{ type: 'error', message: err?.message || 'Could not parse template' }
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
setIsLoading(false);
|
||||||
}, [content, fields, snap.scriptLogs])
|
}, [content, fields, snap.scriptLogs])
|
||||||
|
|
||||||
useEffect(() => {
|
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 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.')
|
if (isDisabled) return toast.error('Please fill in all the required fields.')
|
||||||
|
|
||||||
state.scriptLogs = []
|
state.scriptLogs = []
|
||||||
runScript()
|
await runScript();
|
||||||
setIsDialogOpen(false)
|
setIsDialogOpen(false)
|
||||||
}, [isDisabled, runScript])
|
}, [isDisabled, runScript])
|
||||||
|
|
||||||
@@ -279,7 +300,7 @@ const RunScript: React.FC<{ file: IFile }> = ({ file: { content, name } }) => {
|
|||||||
<DialogClose asChild>
|
<DialogClose asChild>
|
||||||
<Button outline>Cancel</Button>
|
<Button outline>Cancel</Button>
|
||||||
</DialogClose>
|
</DialogClose>
|
||||||
<Button variant="primary" isDisabled={isDisabled} onClick={handleRun}>
|
<Button variant="primary" isDisabled={isDisabled || isLoading} isLoading={isLoading} onClick={handleRun}>
|
||||||
Run script
|
Run script
|
||||||
</Button>
|
</Button>
|
||||||
</Flex>
|
</Flex>
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ import {
|
|||||||
} from './Dialog'
|
} from './Dialog'
|
||||||
import { Plus, X } from 'phosphor-react'
|
import { Plus, X } from 'phosphor-react'
|
||||||
import { styled } from '../stitches.config'
|
import { styled } from '../stitches.config'
|
||||||
import { capitalize } from '../utils/helpers'
|
import { capitalize, getFileExtention } from '../utils/helpers'
|
||||||
import ContextMenu, { ContentMenuOption } from './ContextMenu'
|
import ContextMenu, { ContentMenuOption } from './ContextMenu'
|
||||||
|
|
||||||
const ErrorText = styled(Text, {
|
const ErrorText = styled(Text, {
|
||||||
@@ -97,7 +97,7 @@ export const Tabs = ({
|
|||||||
if (!tabname) {
|
if (!tabname) {
|
||||||
return { error: `Please enter ${label.toLocaleLowerCase()} name.` }
|
return { error: `Please enter ${label.toLocaleLowerCase()} name.` }
|
||||||
}
|
}
|
||||||
let ext = (tabname.includes('.') && tabname.split('.').pop()) || ''
|
let ext = getFileExtention(tabname)
|
||||||
|
|
||||||
if (!ext && defaultExtension) {
|
if (!ext && defaultExtension) {
|
||||||
ext = defaultExtension
|
ext = defaultExtension
|
||||||
@@ -109,7 +109,7 @@ export const Tabs = ({
|
|||||||
if (extensionRequired && !ext) {
|
if (extensionRequired && !ext) {
|
||||||
return { error: 'File extension is required!' }
|
return { error: 'File extension is required!' }
|
||||||
}
|
}
|
||||||
if (allowedExtensions && !allowedExtensions.includes(ext)) {
|
if (allowedExtensions && ext && !allowedExtensions.includes(ext)) {
|
||||||
return { error: 'This file extension is not allowed!' }
|
return { error: 'This file extension is not allowed!' }
|
||||||
}
|
}
|
||||||
if (headerExtraValidation && !tabname.match(headerExtraValidation.regex)) {
|
if (headerExtraValidation && !tabname.match(headerExtraValidation.regex)) {
|
||||||
|
|||||||
@@ -72,7 +72,12 @@ const Tooltip: React.FC<React.ComponentProps<typeof StyledContent> & ITooltip> =
|
|||||||
...rest
|
...rest
|
||||||
}) => {
|
}) => {
|
||||||
return (
|
return (
|
||||||
<TooltipPrimitive.Root open={open} defaultOpen={defaultOpen} onOpenChange={onOpenChange}>
|
<TooltipPrimitive.Root
|
||||||
|
open={open}
|
||||||
|
defaultOpen={defaultOpen}
|
||||||
|
onOpenChange={onOpenChange}
|
||||||
|
delayDuration={100}
|
||||||
|
>
|
||||||
<TooltipPrimitive.Trigger asChild>{children}</TooltipPrimitive.Trigger>
|
<TooltipPrimitive.Trigger asChild>{children}</TooltipPrimitive.Trigger>
|
||||||
<StyledContent side="bottom" align="center" {...rest}>
|
<StyledContent side="bottom" align="center" {...rest}>
|
||||||
<div dangerouslySetInnerHTML={{ __html: content }} />
|
<div dangerouslySetInnerHTML={{ __html: content }} />
|
||||||
|
|||||||
@@ -19,6 +19,7 @@ import { TxJson } from './json'
|
|||||||
import { TxUI } from './ui'
|
import { TxUI } from './ui'
|
||||||
import { default as _estimateFee } from '../../utils/estimateFee'
|
import { default as _estimateFee } from '../../utils/estimateFee'
|
||||||
import toast from 'react-hot-toast'
|
import toast from 'react-hot-toast'
|
||||||
|
import { combineFlags, extractFlags, transactionFlags } from '../../state/constants/flags'
|
||||||
|
|
||||||
export interface TransactionProps {
|
export interface TransactionProps {
|
||||||
header: string
|
header: string
|
||||||
@@ -39,14 +40,17 @@ const Transaction: FC<TransactionProps> = ({ header, state: txState, ...props })
|
|||||||
|
|
||||||
const prepareOptions = useCallback(
|
const prepareOptions = useCallback(
|
||||||
(state: Partial<TransactionState> = txState) => {
|
(state: Partial<TransactionState> = txState) => {
|
||||||
const { selectedTransaction, selectedDestAccount, selectedAccount, txFields } = state
|
const { selectedTransaction, selectedDestAccount, selectedAccount, txFields, selectedFlags } =
|
||||||
|
state
|
||||||
|
|
||||||
const TransactionType = selectedTransaction?.value || null
|
const TransactionType = selectedTransaction?.value || null
|
||||||
const Destination = selectedDestAccount?.value || txFields?.Destination
|
const Destination = selectedDestAccount?.value || txFields?.Destination
|
||||||
const Account = selectedAccount?.value || null
|
const Account = selectedAccount?.value || null
|
||||||
|
const Flags = combineFlags(selectedFlags?.map(flag => flag.value)) || txFields?.Flags
|
||||||
|
|
||||||
return prepareTransaction({
|
return prepareTransaction({
|
||||||
...txFields,
|
...txFields,
|
||||||
|
Flags,
|
||||||
TransactionType,
|
TransactionType,
|
||||||
Destination,
|
Destination,
|
||||||
Account
|
Account
|
||||||
@@ -136,8 +140,13 @@ const Transaction: FC<TransactionProps> = ({ header, state: txState, ...props })
|
|||||||
} else {
|
} else {
|
||||||
fields.Destination = undefined
|
fields.Destination = undefined
|
||||||
}
|
}
|
||||||
nwState.txFields = fields
|
|
||||||
|
|
||||||
|
if (transactionType?.value && transactionFlags[transactionType?.value] && fields.Flags) {
|
||||||
|
nwState.selectedFlags = extractFlags(transactionType.value, fields.Flags)
|
||||||
|
fields.Flags = undefined
|
||||||
|
}
|
||||||
|
|
||||||
|
nwState.txFields = fields
|
||||||
const state = modifyTxState(header, nwState, { replaceState: true })
|
const state = modifyTxState(header, nwState, { replaceState: true })
|
||||||
const editorValue = getJsonString(state)
|
const editorValue = getJsonString(state)
|
||||||
return setState({ editorValue })
|
return setState({ editorValue })
|
||||||
@@ -179,7 +188,12 @@ const Transaction: FC<TransactionProps> = ({ header, state: txState, ...props })
|
|||||||
estimateFee={estimateFee}
|
estimateFee={estimateFee}
|
||||||
/>
|
/>
|
||||||
) : (
|
) : (
|
||||||
<TxUI state={txState} setState={setState} estimateFee={estimateFee} />
|
<TxUI
|
||||||
|
state={txState}
|
||||||
|
resetState={resetState}
|
||||||
|
setState={setState}
|
||||||
|
estimateFee={estimateFee}
|
||||||
|
/>
|
||||||
)}
|
)}
|
||||||
<Flex
|
<Flex
|
||||||
row
|
row
|
||||||
|
|||||||
@@ -17,16 +17,19 @@ import state from '../../state'
|
|||||||
import { streamState } from '../DebugStream'
|
import { streamState } from '../DebugStream'
|
||||||
import { Button } from '..'
|
import { Button } from '..'
|
||||||
import Textarea from '../Textarea'
|
import Textarea from '../Textarea'
|
||||||
|
import { getFlags } from '../../state/constants/flags'
|
||||||
|
|
||||||
interface UIProps {
|
interface UIProps {
|
||||||
setState: (pTx?: Partial<TransactionState> | undefined) => TransactionState | undefined
|
setState: (pTx?: Partial<TransactionState> | undefined) => TransactionState | undefined
|
||||||
|
resetState: (tt?: SelectOption) => TransactionState | undefined
|
||||||
state: TransactionState
|
state: TransactionState
|
||||||
estimateFee?: (...arg: any) => Promise<string | undefined>
|
estimateFee?: (...arg: any) => Promise<string | undefined>
|
||||||
}
|
}
|
||||||
|
|
||||||
export const TxUI: FC<UIProps> = ({ state: txState, setState, estimateFee }) => {
|
export const TxUI: FC<UIProps> = ({ state: txState, setState, resetState, estimateFee }) => {
|
||||||
const { accounts } = useSnapshot(state)
|
const { accounts } = useSnapshot(state)
|
||||||
const { selectedAccount, selectedDestAccount, selectedTransaction, txFields } = txState
|
const { selectedAccount, selectedDestAccount, selectedTransaction, txFields, selectedFlags } =
|
||||||
|
txState
|
||||||
|
|
||||||
const accountOptions: SelectOption[] = accounts.map(acc => ({
|
const accountOptions: SelectOption[] = accounts.map(acc => ({
|
||||||
label: acc.name,
|
label: acc.name,
|
||||||
@@ -40,23 +43,15 @@ export const TxUI: FC<UIProps> = ({ state: txState, setState, estimateFee }) =>
|
|||||||
}))
|
}))
|
||||||
.filter(acc => acc.value !== selectedAccount?.value)
|
.filter(acc => acc.value !== selectedAccount?.value)
|
||||||
|
|
||||||
|
const flagsOptions: SelectOption[] = Object.entries(
|
||||||
|
getFlags(selectedTransaction?.value) || {}
|
||||||
|
).map(([label, value]) => ({
|
||||||
|
label,
|
||||||
|
value
|
||||||
|
}))
|
||||||
|
|
||||||
const [feeLoading, setFeeLoading] = useState(false)
|
const [feeLoading, setFeeLoading] = useState(false)
|
||||||
|
|
||||||
const resetFields = useCallback(
|
|
||||||
(tt: string) => {
|
|
||||||
const fields = getTxFields(tt)
|
|
||||||
|
|
||||||
if (fields.Destination !== undefined) {
|
|
||||||
setState({ selectedDestAccount: null })
|
|
||||||
fields.Destination = ''
|
|
||||||
} else {
|
|
||||||
fields.Destination = undefined
|
|
||||||
}
|
|
||||||
return setState({ txFields: fields })
|
|
||||||
},
|
|
||||||
[setState]
|
|
||||||
)
|
|
||||||
|
|
||||||
const handleSetAccount = (acc: SelectOption) => {
|
const handleSetAccount = (acc: SelectOption) => {
|
||||||
setState({ selectedAccount: acc })
|
setState({ selectedAccount: acc })
|
||||||
streamState.selectedAccount = acc
|
streamState.selectedAccount = acc
|
||||||
@@ -92,11 +87,11 @@ export const TxUI: FC<UIProps> = ({ state: txState, setState, estimateFee }) =>
|
|||||||
(tt: SelectOption) => {
|
(tt: SelectOption) => {
|
||||||
setState({ selectedTransaction: tt })
|
setState({ selectedTransaction: tt })
|
||||||
|
|
||||||
const newState = resetFields(tt.value)
|
const newState = resetState(tt)
|
||||||
|
|
||||||
handleEstimateFee(newState, true)
|
handleEstimateFee(newState, true)
|
||||||
},
|
},
|
||||||
[handleEstimateFee, resetFields, setState]
|
[handleEstimateFee, resetState, setState]
|
||||||
)
|
)
|
||||||
|
|
||||||
const switchToJson = () => setState({ viewType: 'json' })
|
const switchToJson = () => setState({ viewType: 'json' })
|
||||||
@@ -115,14 +110,16 @@ export const TxUI: FC<UIProps> = ({ state: txState, setState, estimateFee }) =>
|
|||||||
[selectedTransaction?.value]
|
[selectedTransaction?.value]
|
||||||
)
|
)
|
||||||
|
|
||||||
const specialFields = ['TransactionType', 'Account']
|
const richFields = ['TransactionType', 'Account']
|
||||||
if (fields.Destination !== undefined) {
|
if (fields.Destination !== undefined) {
|
||||||
specialFields.push('Destination')
|
richFields.push('Destination')
|
||||||
}
|
}
|
||||||
|
|
||||||
const otherFields = Object.keys(txFields).filter(k => !specialFields.includes(k)) as [
|
if (flagsOptions.length) {
|
||||||
keyof TxFields
|
richFields.push('Flags')
|
||||||
]
|
}
|
||||||
|
|
||||||
|
const otherFields = Object.keys(txFields).filter(k => !richFields.includes(k)) as [keyof TxFields]
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Container
|
<Container
|
||||||
@@ -179,7 +176,32 @@ export const TxUI: FC<UIProps> = ({ state: txState, setState, estimateFee }) =>
|
|||||||
onChange={(acc: any) => handleSetAccount(acc)} // TODO make react-select have correct types for acc
|
onChange={(acc: any) => handleSetAccount(acc)} // TODO make react-select have correct types for acc
|
||||||
/>
|
/>
|
||||||
</Flex>
|
</Flex>
|
||||||
{fields.Destination !== undefined && (
|
{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>
|
||||||
|
<Select
|
||||||
|
instanceId="to-account"
|
||||||
|
placeholder="Select the destination account"
|
||||||
|
css={{ width: '70%' }}
|
||||||
|
options={destAccountOptions}
|
||||||
|
value={selectedDestAccount}
|
||||||
|
isClearable
|
||||||
|
onChange={(acc: any) => setState({ selectedDestAccount: acc })}
|
||||||
|
/>
|
||||||
|
</Flex>
|
||||||
|
)}
|
||||||
|
{richFields.includes('Flags') && (
|
||||||
<Flex
|
<Flex
|
||||||
row
|
row
|
||||||
fluid
|
fluid
|
||||||
@@ -191,16 +213,21 @@ export const TxUI: FC<UIProps> = ({ state: txState, setState, estimateFee }) =>
|
|||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Text muted css={{ mr: '$3' }}>
|
<Text muted css={{ mr: '$3' }}>
|
||||||
Destination account:{' '}
|
Flags:{' '}
|
||||||
</Text>
|
</Text>
|
||||||
<Select
|
<Select
|
||||||
instanceId="to-account"
|
|
||||||
placeholder="Select the destination account"
|
|
||||||
css={{ width: '70%' }}
|
|
||||||
options={destAccountOptions}
|
|
||||||
value={selectedDestAccount}
|
|
||||||
isClearable
|
isClearable
|
||||||
onChange={(acc: any) => setState({ selectedDestAccount: acc })}
|
css={{ width: '70%' }}
|
||||||
|
instanceId="flags"
|
||||||
|
placeholder="Select flags to apply"
|
||||||
|
menuPosition="fixed"
|
||||||
|
value={selectedFlags}
|
||||||
|
isMulti
|
||||||
|
options={flagsOptions}
|
||||||
|
onChange={flags => setState({ selectedFlags: flags as any })}
|
||||||
|
closeMenuOnSelect={
|
||||||
|
selectedFlags ? selectedFlags.length >= flagsOptions.length - 1 : false
|
||||||
|
}
|
||||||
/>
|
/>
|
||||||
</Flex>
|
</Flex>
|
||||||
)}
|
)}
|
||||||
|
|||||||
409
content/hook-set-codes.json
Normal file
409
content/hook-set-codes.json
Normal file
@@ -0,0 +1,409 @@
|
|||||||
|
[
|
||||||
|
{
|
||||||
|
"code": 1,
|
||||||
|
"identifier": "AMENDMENT_DISABLED",
|
||||||
|
"description": "attempt to HookSet when amendment is not yet enabled."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"code": 2,
|
||||||
|
"identifier": "API_ILLEGAL",
|
||||||
|
"description": "HookSet object contained HookApiVersion for existing HookDefinition"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"code": 3,
|
||||||
|
"identifier": "API_INVALID",
|
||||||
|
"description": "HookSet object contained HookApiVersion for unrecognised hook API "
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"code": 4,
|
||||||
|
"identifier": "API_MISSING",
|
||||||
|
"description": "HookSet object lacked HookApiVersion"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"code": 5,
|
||||||
|
"identifier": "BLOCK_ILLEGAL",
|
||||||
|
"description": " a block end instruction moves execution below depth 0 {{}}`}` <= like this"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"code": 6,
|
||||||
|
"identifier": "CALL_ILLEGAL",
|
||||||
|
"description": "wasm tries to call a non-whitelisted function"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"code": 7,
|
||||||
|
"identifier": "CALL_INDIRECT",
|
||||||
|
"description": "wasm used call indirect instruction which is disallowed"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"code": 8,
|
||||||
|
"identifier": "CREATE_FLAG",
|
||||||
|
"description": "create operation requires hsoOVERRIDE"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"code": 9,
|
||||||
|
"identifier": "DELETE_FIELD",
|
||||||
|
"description": ""
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"code": 10,
|
||||||
|
"identifier": "DELETE_FLAG",
|
||||||
|
"description": "delete operation requires hsoOVERRIDE"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"code": 11,
|
||||||
|
"identifier": "DELETE_NOTHING",
|
||||||
|
"description": "delete operation would delete nothing"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"code": 12,
|
||||||
|
"identifier": "EXPORTS_MISSING",
|
||||||
|
"description": "hook did not export *any* functions (should be cbak, hook)"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"code": 13,
|
||||||
|
"identifier": "EXPORT_CBAK_FUNC",
|
||||||
|
"description": "hook did not export correct func def int64_t cbak(uint32_t)"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"code": 14,
|
||||||
|
"identifier": "EXPORT_HOOK_FUNC",
|
||||||
|
"description": "hook did not export correct func def int64_t hook(uint32_t)"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"code": 15,
|
||||||
|
"identifier": "EXPORT_MISSING",
|
||||||
|
"description": "distinct from export*S*_missing, either hook or cbak is missing"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"identifier": "FLAGS_INVALID",
|
||||||
|
"code": 16,
|
||||||
|
"description": "HookSet flags were invalid for specified operation "
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"identifier": "FUNCS_MISSING",
|
||||||
|
"code": 17,
|
||||||
|
"description": "hook did not include function code for any functions "
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"identifier": "FUNC_PARAM_INVALID",
|
||||||
|
"code": 18,
|
||||||
|
"description": "parameter types may only be i32 i64 u32 u64 "
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"identifier": "FUNC_RETURN_COUNT",
|
||||||
|
"code": 19,
|
||||||
|
"description": "a function type is defined in the wasm which returns > 1 return value "
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"identifier": "FUNC_RETURN_INVALID",
|
||||||
|
"code": 20,
|
||||||
|
"description": "a function type does not return i32 i64 u32 or u64 "
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"identifier": "FUNC_TYPELESS",
|
||||||
|
"code": 21,
|
||||||
|
"description": "hook defined hook/cbak but their type is not defined in wasm "
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"identifier": "FUNC_TYPE_INVALID",
|
||||||
|
"code": 22,
|
||||||
|
"description": "malformed and illegal wasm in the func type section "
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"identifier": "GRANTS_EMPTY",
|
||||||
|
"code": 23,
|
||||||
|
"description": "HookSet object contained an empty grants array (you should remove it) "
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"identifier": "GRANTS_EXCESS",
|
||||||
|
"code": 24,
|
||||||
|
"description": "HookSet object cotnained a grants array with too many grants "
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"identifier": "GRANTS_FIELD",
|
||||||
|
"code": 25,
|
||||||
|
"description": "HookSet object contained a grant without Authorize or HookHash "
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"identifier": "GRANTS_ILLEGAL",
|
||||||
|
"code": 26,
|
||||||
|
"description": "Hookset object contained grants array which contained a non Grant object "
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"identifier": "GUARD_IMPORT",
|
||||||
|
"code": 27,
|
||||||
|
"description": "guard import is missing "
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"identifier": "GUARD_MISSING",
|
||||||
|
"code": 28,
|
||||||
|
"description": "guard call missing at top of loop "
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"identifier": "GUARD_PARAMETERS",
|
||||||
|
"code": 29,
|
||||||
|
"description": "guard called but did not use constant expressions for params "
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"identifier": "HASH_OR_CODE",
|
||||||
|
"code": 30,
|
||||||
|
"description": "HookSet object can contain only one of CreateCode and HookHash "
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"identifier": "HOOKON_MISSING",
|
||||||
|
"code": 31,
|
||||||
|
"description": "HookSet object did not contain HookOn but should have "
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"identifier": "HOOKS_ARRAY_BAD",
|
||||||
|
"code": 32,
|
||||||
|
"description": "attempt to HookSet with a Hooks array containing a non-Hook obj "
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"identifier": "HOOKS_ARRAY_BLANK",
|
||||||
|
"code": 33,
|
||||||
|
"description": "all hook set objs were blank "
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"identifier": "HOOKS_ARRAY_EMPTY",
|
||||||
|
"code": 34,
|
||||||
|
"description": "attempt to HookSet with an empty Hooks array "
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"identifier": "HOOKS_ARRAY_MISSING",
|
||||||
|
"code": 35,
|
||||||
|
"description": "attempt to HookSet without a Hooks array "
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"identifier": "HOOKS_ARRAY_TOO_BIG",
|
||||||
|
"code": 36,
|
||||||
|
"description": "attempt to HookSet with a Hooks array beyond the chain size limit "
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"identifier": "HOOK_ADD",
|
||||||
|
"code": 37,
|
||||||
|
"description": "Informational: adding ltHook to directory "
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"identifier": "HOOK_DEF_MISSING",
|
||||||
|
"code": 38,
|
||||||
|
"description": "attempt to reference a hook definition (by hash) that is not on ledger "
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"identifier": "HOOK_DELETE",
|
||||||
|
"code": 39,
|
||||||
|
"description": "unable to delete ltHook from owner "
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"identifier": "HOOK_INVALID_FIELD",
|
||||||
|
"code": 40,
|
||||||
|
"description": "HookSetObj contained an illegal/unexpected field "
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"identifier": "HOOK_PARAMS_COUNT",
|
||||||
|
"code": 41,
|
||||||
|
"description": "hookset obj would create too many hook parameters "
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"identifier": "HOOK_PARAM_SIZE",
|
||||||
|
"code": 42,
|
||||||
|
"description": "hookset obj sets a parameter or value that exceeds max allowable size "
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"identifier": "IMPORTS_MISSING",
|
||||||
|
"code": 43,
|
||||||
|
"description": "hook must import guard, and accept/rollback "
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"identifier": "IMPORT_ILLEGAL",
|
||||||
|
"code": 44,
|
||||||
|
"description": "attempted import of a non-whitelisted function "
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"identifier": "IMPORT_MODULE_BAD",
|
||||||
|
"code": 45,
|
||||||
|
"description": "hook attempted to specify no or a bad import module "
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"identifier": "IMPORT_MODULE_ENV",
|
||||||
|
"code": 46,
|
||||||
|
"description": "hook attempted to specify import module not named env "
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"identifier": "IMPORT_NAME_BAD",
|
||||||
|
"code": 47,
|
||||||
|
"description": "import name was too short or too long "
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"identifier": "INSTALL_FLAG",
|
||||||
|
"code": 48,
|
||||||
|
"description": "install operation requires hsoOVERRIDE "
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"identifier": "INSTALL_MISSING",
|
||||||
|
"code": 49,
|
||||||
|
"description": "install operation specifies hookhash which doesn't exist on the ledger "
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"identifier": "INSTRUCTION_COUNT",
|
||||||
|
"code": 50,
|
||||||
|
"description": "worst case execution instruction count as computed by HookSet "
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"identifier": "INSTRUCTION_EXCESS",
|
||||||
|
"code": 51,
|
||||||
|
"description": "worst case execution instruction count was too large "
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"identifier": "MEMORY_GROW",
|
||||||
|
"code": 52,
|
||||||
|
"description": "memory.grow instruction is present but disallowed "
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"identifier": "NAMESPACE_MISSING",
|
||||||
|
"code": 53,
|
||||||
|
"description": "HookSet object lacked HookNamespace "
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"identifier": "NSDELETE",
|
||||||
|
"code": 54,
|
||||||
|
"description": "Informational: a namespace is being deleted "
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"identifier": "NSDELETE_ACCOUNT",
|
||||||
|
"code": 55,
|
||||||
|
"description": "nsdelete tried to delete ns from a non-existing account "
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"identifier": "NSDELETE_COUNT",
|
||||||
|
"code": 56,
|
||||||
|
"description": "namespace state count less than 0 / overflow "
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"identifier": "NSDELETE_DIR",
|
||||||
|
"code": 57,
|
||||||
|
"description": "could not delete directory node in ledger "
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"identifier": "NSDELETE_DIRECTORY",
|
||||||
|
"code": 58,
|
||||||
|
"description": "nsdelete operation failed to delete ns directory "
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"identifier": "NSDELETE_DIR_ENTRY",
|
||||||
|
"code": 59,
|
||||||
|
"description": "nsdelete operation failed due to bad entry in ns directory "
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"identifier": "NSDELETE_ENTRY",
|
||||||
|
"code": 60,
|
||||||
|
"description": "nsdelete operation failed due to missing hook state entry "
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"identifier": "NSDELETE_FIELD",
|
||||||
|
"code": 61
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"identifier": "NSDELETE_FLAGS",
|
||||||
|
"code": 62
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"identifier": "NSDELETE_NONSTATE",
|
||||||
|
"code": 63,
|
||||||
|
"description": "nsdelete operation failed due to the presence of a non-hookstate obj "
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"identifier": "NSDELETE_NOTHING",
|
||||||
|
"code": 64,
|
||||||
|
"description": "hsfNSDELETE provided but nothing to delete "
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"identifier": "OPERATION_INVALID",
|
||||||
|
"code": 65,
|
||||||
|
"description": "could not deduce an operation from the provided hookset obj "
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"identifier": "OVERRIDE_MISSING",
|
||||||
|
"code": 66,
|
||||||
|
"description": "HookSet object was trying to update or delete a hook but lacked hsfOVERRIDE "
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"identifier": "PARAMETERS_FIELD",
|
||||||
|
"code": 67,
|
||||||
|
"description": "HookParameters contained a HookParameter with an invalid key in it "
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"identifier": "PARAMETERS_ILLEGAL",
|
||||||
|
"code": 68,
|
||||||
|
"description": "HookParameters contained something other than a HookParameter "
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"identifier": "PARAMETERS_NAME",
|
||||||
|
"code": 69,
|
||||||
|
"description": "HookParameters contained a HookParameter which lacked ParameterName field "
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"identifier": "PARAM_HOOK_CBAK",
|
||||||
|
"code": 70,
|
||||||
|
"description": "hook and cbak must take exactly one u32 parameter "
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"identifier": "RETURN_HOOK_CBAK",
|
||||||
|
"code": 71,
|
||||||
|
"description": "hook and cbak must retunr i64 "
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"identifier": "SHORT_HOOK",
|
||||||
|
"code": 72,
|
||||||
|
"description": "web assembly byte code ended abruptly "
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"identifier": "TYPE_INVALID",
|
||||||
|
"code": 73,
|
||||||
|
"description": "malformed and illegal wasm specifying an illegal local var type "
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"identifier": "WASM_BAD_MAGIC",
|
||||||
|
"code": 74,
|
||||||
|
"description": "wasm magic number missing or not wasm "
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"identifier": "WASM_INVALID",
|
||||||
|
"code": 75,
|
||||||
|
"description": "set hook operation would set invalid wasm "
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"identifier": "WASM_PARSE_LOOP",
|
||||||
|
"code": 76,
|
||||||
|
"description": "wasm section parsing resulted in an infinite loop "
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"identifier": "WASM_SMOKE_TEST",
|
||||||
|
"code": 77,
|
||||||
|
"description": "Informational: first attempt to load wasm into wasm runtime "
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"identifier": "WASM_TEST_FAILURE",
|
||||||
|
"code": 78,
|
||||||
|
"description": "the smoke test failed "
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"identifier": "WASM_TOO_BIG",
|
||||||
|
"code": 79,
|
||||||
|
"description": "set hook would exceed maximum hook size "
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"identifier": "WASM_TOO_SMALL",
|
||||||
|
"code": 80
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"identifier": "WASM_VALIDATION",
|
||||||
|
"code": 81,
|
||||||
|
"description": "a generic error while parsing wasm, usually leb128 overflow"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"identifier": "HOOK_CBAK_DIFF_TYPES",
|
||||||
|
"code": 82,
|
||||||
|
"description": "hook and cbak function definitions were different"
|
||||||
|
}
|
||||||
|
]
|
||||||
@@ -6,7 +6,7 @@
|
|||||||
"DestinationTag": 13,
|
"DestinationTag": 13,
|
||||||
"Fee": "2000000",
|
"Fee": "2000000",
|
||||||
"Sequence": 2470665,
|
"Sequence": 2470665,
|
||||||
"Flags": 2147483648
|
"Flags": "2147483648"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"TransactionType": "AccountSet",
|
"TransactionType": "AccountSet",
|
||||||
@@ -48,7 +48,7 @@
|
|||||||
"Account": "rsUiUMpnrgxQp24dJYZDhmV4bE3aBtQyt8",
|
"Account": "rsUiUMpnrgxQp24dJYZDhmV4bE3aBtQyt8",
|
||||||
"Authorize": "rEhxGqkqPPSxQ3P25J66ft5TwpzV14k2de",
|
"Authorize": "rEhxGqkqPPSxQ3P25J66ft5TwpzV14k2de",
|
||||||
"Fee": "10",
|
"Fee": "10",
|
||||||
"Flags": 2147483648,
|
"Flags": "2147483648",
|
||||||
"Sequence": 2
|
"Sequence": 2
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -109,7 +109,9 @@
|
|||||||
"Fee": "10",
|
"Fee": "10",
|
||||||
"NFTokenOffers": {
|
"NFTokenOffers": {
|
||||||
"$type": "json",
|
"$type": "json",
|
||||||
"$value": ["4AAAEEA76E3C8148473CB3840CE637676E561FB02BD4CA22CA59729EA815B862"]
|
"$value": [
|
||||||
|
"4AAAEEA76E3C8148473CB3840CE637676E561FB02BD4CA22CA59729EA815B862"
|
||||||
|
]
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -120,7 +122,7 @@
|
|||||||
"$value": "100",
|
"$value": "100",
|
||||||
"$type": "xrp"
|
"$type": "xrp"
|
||||||
},
|
},
|
||||||
"Flags": 1,
|
"Flags": "1",
|
||||||
"Destination": "",
|
"Destination": "",
|
||||||
"Fee": "10"
|
"Fee": "10"
|
||||||
},
|
},
|
||||||
@@ -128,7 +130,7 @@
|
|||||||
"TransactionType": "OfferCancel",
|
"TransactionType": "OfferCancel",
|
||||||
"Account": "ra5nK24KXen9AHvsdFTKHSANinZseWnPcX",
|
"Account": "ra5nK24KXen9AHvsdFTKHSANinZseWnPcX",
|
||||||
"Fee": "12",
|
"Fee": "12",
|
||||||
"Flags": 0,
|
"Flags": "0",
|
||||||
"LastLedgerSequence": 7108629,
|
"LastLedgerSequence": 7108629,
|
||||||
"OfferSequence": 6,
|
"OfferSequence": 6,
|
||||||
"Sequence": 7
|
"Sequence": 7
|
||||||
@@ -137,7 +139,7 @@
|
|||||||
"TransactionType": "OfferCreate",
|
"TransactionType": "OfferCreate",
|
||||||
"Account": "ra5nK24KXen9AHvsdFTKHSANinZseWnPcX",
|
"Account": "ra5nK24KXen9AHvsdFTKHSANinZseWnPcX",
|
||||||
"Fee": "12",
|
"Fee": "12",
|
||||||
"Flags": 0,
|
"Flags": "0",
|
||||||
"LastLedgerSequence": 7108682,
|
"LastLedgerSequence": 7108682,
|
||||||
"Sequence": 8,
|
"Sequence": 8,
|
||||||
"TakerGets": "6000000",
|
"TakerGets": "6000000",
|
||||||
@@ -155,7 +157,7 @@
|
|||||||
"$type": "xrp"
|
"$type": "xrp"
|
||||||
},
|
},
|
||||||
"Fee": "12",
|
"Fee": "12",
|
||||||
"Flags": 2147483648,
|
"Flags": "2147483648",
|
||||||
"Sequence": 2
|
"Sequence": 2
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -185,14 +187,14 @@
|
|||||||
"Fee": "10"
|
"Fee": "10"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"Flags": 0,
|
"Flags": "0",
|
||||||
"TransactionType": "SetRegularKey",
|
"TransactionType": "SetRegularKey",
|
||||||
"Account": "rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn",
|
"Account": "rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn",
|
||||||
"Fee": "12",
|
"Fee": "12",
|
||||||
"RegularKey": "rAR8rR8sUkBoCZFawhkWzY4Y5YoyuznwD"
|
"RegularKey": "rAR8rR8sUkBoCZFawhkWzY4Y5YoyuznwD"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"Flags": 0,
|
"Flags": "0",
|
||||||
"TransactionType": "SignerListSet",
|
"TransactionType": "SignerListSet",
|
||||||
"Account": "rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn",
|
"Account": "rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn",
|
||||||
"Fee": "12",
|
"Fee": "12",
|
||||||
@@ -232,7 +234,7 @@
|
|||||||
"TransactionType": "TrustSet",
|
"TransactionType": "TrustSet",
|
||||||
"Account": "ra5nK24KXen9AHvsdFTKHSANinZseWnPcX",
|
"Account": "ra5nK24KXen9AHvsdFTKHSANinZseWnPcX",
|
||||||
"Fee": "12",
|
"Fee": "12",
|
||||||
"Flags": 262144,
|
"Flags": "262144",
|
||||||
"LastLedgerSequence": 8007750,
|
"LastLedgerSequence": 8007750,
|
||||||
"LimitAmount": {
|
"LimitAmount": {
|
||||||
"$type": "json",
|
"$type": "json",
|
||||||
@@ -243,5 +245,14 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"Sequence": 12
|
"Sequence": 12
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"TransactionType": "Invoke",
|
||||||
|
"Fee": "12"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"TransactionType": "UriToken",
|
||||||
|
"Fee": "12",
|
||||||
|
"URI": "697066733A2F2F516D614374444B5A4656767666756676626479346573745A626851483744586831364354707631686F776D424779"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
@@ -12,7 +12,7 @@ module.exports = {
|
|||||||
config.resolve.fallback.fs = false
|
config.resolve.fallback.fs = false
|
||||||
}
|
}
|
||||||
config.module.rules.push({
|
config.module.rules.push({
|
||||||
test: /\.md$/,
|
test: [/\.md$/, /hook-bundle\.js$/],
|
||||||
use: 'raw-loader'
|
use: 'raw-loader'
|
||||||
})
|
})
|
||||||
return config
|
return config
|
||||||
|
|||||||
10
package.json
10
package.json
@@ -8,7 +8,8 @@
|
|||||||
"start": "next start",
|
"start": "next start",
|
||||||
"lint": "next lint",
|
"lint": "next lint",
|
||||||
"format": "prettier --write .",
|
"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": {
|
"dependencies": {
|
||||||
"@codingame/monaco-jsonrpc": "^0.3.1",
|
"@codingame/monaco-jsonrpc": "^0.3.1",
|
||||||
@@ -64,9 +65,9 @@
|
|||||||
"valtio": "^1.2.5",
|
"valtio": "^1.2.5",
|
||||||
"vscode-languageserver": "^7.0.0",
|
"vscode-languageserver": "^7.0.0",
|
||||||
"vscode-uri": "^3.0.2",
|
"vscode-uri": "^3.0.2",
|
||||||
"wabt": "1.0.16",
|
"wabt": "^1.0.30",
|
||||||
"xrpl-accountlib": "^1.5.2",
|
"xrpl-accountlib": "^1.6.1",
|
||||||
"xrpl-client": "^1.9.4"
|
"xrpl-client": "^2.0.2"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@types/dinero.js": "^1.9.0",
|
"@types/dinero.js": "^1.9.0",
|
||||||
@@ -75,6 +76,7 @@
|
|||||||
"@types/lodash.xor": "^4.5.6",
|
"@types/lodash.xor": "^4.5.6",
|
||||||
"@types/pako": "^1.0.2",
|
"@types/pako": "^1.0.2",
|
||||||
"@types/react": "17.0.31",
|
"@types/react": "17.0.31",
|
||||||
|
"browserify": "^17.0.0",
|
||||||
"eslint": "7.32.0",
|
"eslint": "7.32.0",
|
||||||
"eslint-config-next": "11.1.2",
|
"eslint-config-next": "11.1.2",
|
||||||
"raw-loader": "^4.0.2",
|
"raw-loader": "^4.0.2",
|
||||||
|
|||||||
@@ -10,10 +10,11 @@ import Box from '../../components/Box'
|
|||||||
import Button from '../../components/Button'
|
import Button from '../../components/Button'
|
||||||
import Popover from '../../components/Popover'
|
import Popover from '../../components/Popover'
|
||||||
import RunScript from '../../components/RunScript'
|
import RunScript from '../../components/RunScript'
|
||||||
import state from '../../state'
|
import state, { IFile } from '../../state'
|
||||||
import { compileCode } from '../../state/actions'
|
import { compileCode } from '../../state/actions'
|
||||||
import { getSplit, saveSplit } from '../../state/actions/persistSplits'
|
import { getSplit, saveSplit } from '../../state/actions/persistSplits'
|
||||||
import { styled } from '../../stitches.config'
|
import { styled } from '../../stitches.config'
|
||||||
|
import { getFileExtention } from '../../utils/helpers'
|
||||||
|
|
||||||
const HooksEditor = dynamic(() => import('../../components/HooksEditor'), {
|
const HooksEditor = dynamic(() => import('../../components/HooksEditor'), {
|
||||||
ssr: false
|
ssr: false
|
||||||
@@ -147,6 +148,9 @@ const CompilerSettings = () => {
|
|||||||
const Home: NextPage = () => {
|
const Home: NextPage = () => {
|
||||||
const snap = useSnapshot(state)
|
const snap = useSnapshot(state)
|
||||||
|
|
||||||
|
const activeFile = snap.files[snap.active] as IFile | undefined
|
||||||
|
const activeFileExt = getFileExtention(activeFile?.name)
|
||||||
|
const canCompile = activeFileExt === 'c' || activeFileExt === 'wat'
|
||||||
return (
|
return (
|
||||||
<Split
|
<Split
|
||||||
direction="vertical"
|
direction="vertical"
|
||||||
@@ -159,7 +163,7 @@ const Home: NextPage = () => {
|
|||||||
>
|
>
|
||||||
<main style={{ display: 'flex', flex: 1, position: 'relative' }}>
|
<main style={{ display: 'flex', flex: 1, position: 'relative' }}>
|
||||||
<HooksEditor />
|
<HooksEditor />
|
||||||
{snap.files[snap.active]?.name?.split('.')?.[1]?.toLowerCase() === 'c' && (
|
{canCompile && (
|
||||||
<Hotkeys
|
<Hotkeys
|
||||||
keyName="command+b,ctrl+b"
|
keyName="command+b,ctrl+b"
|
||||||
onKeyDown={() => !snap.compiling && snap.files.length && compileCode(snap.active)}
|
onKeyDown={() => !snap.compiling && snap.files.length && compileCode(snap.active)}
|
||||||
@@ -193,7 +197,7 @@ const Home: NextPage = () => {
|
|||||||
</Flex>
|
</Flex>
|
||||||
</Hotkeys>
|
</Hotkeys>
|
||||||
)}
|
)}
|
||||||
{snap.files[snap.active]?.name?.split('.')?.[1]?.toLowerCase() === 'js' && (
|
{activeFileExt === 'js' && (
|
||||||
<Hotkeys
|
<Hotkeys
|
||||||
keyName="command+b,ctrl+b"
|
keyName="command+b,ctrl+b"
|
||||||
onKeyDown={() => !snap.compiling && snap.files.length && compileCode(snap.active)}
|
onKeyDown={() => !snap.compiling && snap.files.length && compileCode(snap.active)}
|
||||||
@@ -209,7 +213,7 @@ const Home: NextPage = () => {
|
|||||||
gap: '$2'
|
gap: '$2'
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<RunScript file={snap.files[snap.active]} />
|
<RunScript file={activeFile as IFile} />
|
||||||
</Flex>
|
</Flex>
|
||||||
</Hotkeys>
|
</Hotkeys>
|
||||||
)}
|
)}
|
||||||
@@ -225,7 +229,7 @@ const Home: NextPage = () => {
|
|||||||
>
|
>
|
||||||
<LogBox title="Development Log" clearLog={() => (state.logs = [])} logs={snap.logs} />
|
<LogBox title="Development Log" clearLog={() => (state.logs = [])} logs={snap.logs} />
|
||||||
</Flex>
|
</Flex>
|
||||||
{snap.files[snap.active]?.name?.split('.')?.[1]?.toLowerCase() === 'js' && (
|
{activeFileExt === 'js' && (
|
||||||
<Flex
|
<Flex
|
||||||
css={{
|
css={{
|
||||||
flex: 1
|
flex: 1
|
||||||
|
|||||||
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
|
const content: string
|
||||||
export default content
|
export default content
|
||||||
}
|
}
|
||||||
|
|
||||||
|
declare module '*.hook-bundle.js' {
|
||||||
|
const content: string
|
||||||
|
export default content
|
||||||
|
}
|
||||||
@@ -15,6 +15,11 @@ import { ref } from 'valtio'
|
|||||||
export const compileCode = async (activeId: number) => {
|
export const compileCode = async (activeId: number) => {
|
||||||
// Save the file to global state
|
// Save the file to global state
|
||||||
saveFile(false, activeId)
|
saveFile(false, activeId)
|
||||||
|
const file = state.files[activeId]
|
||||||
|
if (file.name.endsWith('.wat')) {
|
||||||
|
return compileWat(activeId)
|
||||||
|
}
|
||||||
|
|
||||||
if (!process.env.NEXT_PUBLIC_COMPILE_API_ENDPOINT) {
|
if (!process.env.NEXT_PUBLIC_COMPILE_API_ENDPOINT) {
|
||||||
throw Error('Missing env!')
|
throw Error('Missing env!')
|
||||||
}
|
}
|
||||||
@@ -26,7 +31,6 @@ export const compileCode = async (activeId: number) => {
|
|||||||
// Set loading state to true
|
// Set loading state to true
|
||||||
state.compiling = true
|
state.compiling = true
|
||||||
state.logs = []
|
state.logs = []
|
||||||
const file = state.files[activeId]
|
|
||||||
try {
|
try {
|
||||||
file.containsErrors = false
|
file.containsErrors = false
|
||||||
let res: Response
|
let res: Response
|
||||||
@@ -72,7 +76,7 @@ export const compileCode = async (activeId: number) => {
|
|||||||
|
|
||||||
// Import wabt from and create human readable version of wasm file and
|
// Import wabt from and create human readable version of wasm file and
|
||||||
// put it into state
|
// put it into state
|
||||||
const ww = (await import('wabt')).default()
|
const ww = await (await import('wabt')).default()
|
||||||
const myModule = ww.readWasm(new Uint8Array(bufferData), {
|
const myModule = ww.readWasm(new Uint8Array(bufferData), {
|
||||||
readDebugNames: true
|
readDebugNames: true
|
||||||
})
|
})
|
||||||
@@ -122,3 +126,46 @@ export const compileCode = async (activeId: number) => {
|
|||||||
file.containsErrors = true
|
file.containsErrors = true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export const compileWat = async (activeId: number) => {
|
||||||
|
if (state.compiling) return;
|
||||||
|
const file = state.files[activeId]
|
||||||
|
state.compiling = true
|
||||||
|
state.logs = []
|
||||||
|
try {
|
||||||
|
const wabt = await (await import('wabt')).default()
|
||||||
|
const module = wabt.parseWat(file.name, file.content);
|
||||||
|
module.resolveNames();
|
||||||
|
module.validate();
|
||||||
|
const { buffer } = module.toBinary({
|
||||||
|
log: false,
|
||||||
|
write_debug_names: true,
|
||||||
|
});
|
||||||
|
|
||||||
|
file.compiledContent = ref(buffer)
|
||||||
|
file.lastCompiled = new Date()
|
||||||
|
file.compiledValueSnapshot = file.content
|
||||||
|
file.compiledWatContent = file.content
|
||||||
|
|
||||||
|
toast.success('Compiled successfully!', { position: 'bottom-center' })
|
||||||
|
state.logs.push({
|
||||||
|
type: 'success',
|
||||||
|
message: `File ${state.files?.[activeId]?.name} compiled successfully. Ready to deploy.`,
|
||||||
|
link: Router.asPath.replace('develop', 'deploy'),
|
||||||
|
linkText: 'Go to deploy'
|
||||||
|
})
|
||||||
|
} catch (err) {
|
||||||
|
console.log(err)
|
||||||
|
let message = "Error compiling WAT file!"
|
||||||
|
if (err instanceof Error) {
|
||||||
|
message = err.message
|
||||||
|
}
|
||||||
|
state.logs.push({
|
||||||
|
type: 'error',
|
||||||
|
message
|
||||||
|
})
|
||||||
|
toast.error(`Error occurred while compiling!`, { position: 'bottom-center' })
|
||||||
|
file.containsErrors = true
|
||||||
|
}
|
||||||
|
state.compiling = false
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,21 +1,19 @@
|
|||||||
|
import { getFileExtention } from '../../utils/helpers'
|
||||||
import state, { IFile } from '../index'
|
import state, { IFile } from '../index'
|
||||||
|
|
||||||
const languageMapping = {
|
const languageMapping: Record<string, string | undefined> = {
|
||||||
ts: 'typescript',
|
ts: 'typescript',
|
||||||
js: 'javascript',
|
js: 'javascript',
|
||||||
md: 'markdown',
|
md: 'markdown',
|
||||||
c: 'c',
|
c: 'c',
|
||||||
h: 'c',
|
h: 'c',
|
||||||
other: ''
|
txt: 'text'
|
||||||
} /* Initializes empty file to global state */
|
}
|
||||||
|
|
||||||
export const createNewFile = (name: string) => {
|
export const createNewFile = (name: string) => {
|
||||||
const tempName = name.split('.')
|
const ext = getFileExtention(name) || ''
|
||||||
const fileExt = tempName[tempName.length - 1] || 'other'
|
|
||||||
const emptyFile: IFile = {
|
const emptyFile: IFile = { name, language: languageMapping[ext] || 'text', content: '' }
|
||||||
name,
|
|
||||||
language: languageMapping[fileExt as 'ts' | 'js' | 'md' | 'c' | 'h' | 'other'],
|
|
||||||
content: ''
|
|
||||||
}
|
|
||||||
state.files.push(emptyFile)
|
state.files.push(emptyFile)
|
||||||
state.active = state.files.length - 1
|
state.active = state.files.length - 1
|
||||||
}
|
}
|
||||||
@@ -24,5 +22,8 @@ export const renameFile = (oldName: string, nwName: string) => {
|
|||||||
const file = state.files.find(file => file.name === oldName)
|
const file = state.files.find(file => file.name === oldName)
|
||||||
if (!file) throw Error(`No file exists with name ${oldName}`)
|
if (!file) throw Error(`No file exists with name ${oldName}`)
|
||||||
|
|
||||||
|
const ext = getFileExtention(nwName) || ''
|
||||||
|
const language = languageMapping[ext] || 'text'
|
||||||
file.name = nwName
|
file.name = nwName
|
||||||
|
file.language = language
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,6 +7,8 @@ import { Link } from '../../components'
|
|||||||
import { ref } from 'valtio'
|
import { ref } from 'valtio'
|
||||||
import estimateFee from '../../utils/estimateFee'
|
import estimateFee from '../../utils/estimateFee'
|
||||||
import { SetHookData } from '../../utils/setHook'
|
import { SetHookData } from '../../utils/setHook'
|
||||||
|
import ResultLink from '../../components/ResultLink'
|
||||||
|
import { xrplSend } from './xrpl-client'
|
||||||
|
|
||||||
export const sha256 = async (string: string) => {
|
export const sha256 = async (string: string) => {
|
||||||
const utf8 = new TextEncoder().encode(string)
|
const utf8 = new TextEncoder().encode(string)
|
||||||
@@ -63,9 +65,6 @@ export const prepareDeployHookTx = async (
|
|||||||
if (!activeFile?.compiledContent) {
|
if (!activeFile?.compiledContent) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if (!state.client) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
const HookNamespace = (await sha256(data.HookNamespace)).toUpperCase()
|
const HookNamespace = (await sha256(data.HookNamespace)).toUpperCase()
|
||||||
const hookOnValues: (keyof TTS)[] = data.Invoke.map(tt => tt.value)
|
const hookOnValues: (keyof TTS)[] = data.Invoke.map(tt => tt.value)
|
||||||
const { HookParameters } = data
|
const { HookParameters } = data
|
||||||
@@ -86,187 +85,184 @@ export const prepareDeployHookTx = async (
|
|||||||
// }
|
// }
|
||||||
// }
|
// }
|
||||||
// });
|
// });
|
||||||
if (typeof window !== 'undefined') {
|
if (typeof window === 'undefined') return
|
||||||
const tx = {
|
const tx = {
|
||||||
Account: account.address,
|
Account: account.address,
|
||||||
TransactionType: 'SetHook',
|
TransactionType: 'SetHook',
|
||||||
Sequence: account.sequence,
|
Sequence: account.sequence,
|
||||||
Fee: data.Fee,
|
Fee: data.Fee,
|
||||||
Hooks: [
|
NetworkID: process.env.NEXT_PUBLIC_NETWORK_ID,
|
||||||
{
|
Hooks: [
|
||||||
Hook: {
|
{
|
||||||
CreateCode: arrayBufferToHex(activeFile?.compiledContent).toUpperCase(),
|
Hook: {
|
||||||
HookOn: calculateHookOn(hookOnValues),
|
CreateCode: arrayBufferToHex(activeFile?.compiledContent).toUpperCase(),
|
||||||
HookNamespace,
|
HookOn: calculateHookOn(hookOnValues),
|
||||||
HookApiVersion: 0,
|
HookNamespace,
|
||||||
Flags: 1,
|
HookApiVersion: 0,
|
||||||
// ...(filteredHookGrants.length > 0 && { HookGrants: filteredHookGrants }),
|
Flags: 1,
|
||||||
...(filteredHookParameters.length > 0 && {
|
// ...(filteredHookGrants.length > 0 && { HookGrants: filteredHookGrants }),
|
||||||
HookParameters: filteredHookParameters
|
...(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
|
* Turns the wasm binary into hex string, signs the transaction and deploys it to Hooks testnet.
|
||||||
* Hooks testnet.
|
|
||||||
*/
|
*/
|
||||||
export const deployHook = async (account: IAccount & { name?: string }, data: SetHookData) => {
|
export const deployHook = async (account: IAccount & { name?: string }, data: SetHookData) => {
|
||||||
if (typeof window !== 'undefined') {
|
const activeFile = state.files[state.active]?.compiledContent
|
||||||
const activeFile = state.files[state.active]?.compiledContent
|
? state.files[state.active]
|
||||||
? state.files[state.active]
|
: state.files.filter(file => file.compiledContent)[0]
|
||||||
: state.files.filter(file => file.compiledContent)[0]
|
state.deployValues[activeFile.name] = data
|
||||||
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 { signedTransaction } = sign(tx, keypair)
|
const tx = await prepareDeployHookTx(account, data)
|
||||||
const currentAccount = state.accounts.find(acc => acc.address === account.address)
|
if (!tx) {
|
||||||
if (currentAccount) {
|
return
|
||||||
currentAccount.isLoading = true
|
}
|
||||||
}
|
const keypair = derive.familySeed(account.secret)
|
||||||
let submitRes
|
const { signedTransaction } = sign(tx, keypair)
|
||||||
|
|
||||||
try {
|
const currentAccount = state.accounts.find(acc => acc.address === account.address)
|
||||||
submitRes = await state.client?.send({
|
if (currentAccount) {
|
||||||
command: 'submit',
|
currentAccount.isLoading = true
|
||||||
tx_blob: signedTransaction
|
}
|
||||||
|
|
||||||
|
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 ✅'
|
||||||
})
|
})
|
||||||
|
state.deployLogs.push({
|
||||||
if (submitRes.engine_result === 'tesSUCCESS') {
|
type: 'success',
|
||||||
state.deployLogs.push({
|
message: resultMsg
|
||||||
type: 'success',
|
})
|
||||||
message: 'Hook deployed successfully ✅'
|
} else if (submitRes.engine_result) {
|
||||||
})
|
|
||||||
state.deployLogs.push({
|
|
||||||
type: 'success',
|
|
||||||
message: ref(
|
|
||||||
<>
|
|
||||||
[{submitRes.engine_result}] {submitRes.engine_result_message} Transaction hash:{' '}
|
|
||||||
<Link
|
|
||||||
as="a"
|
|
||||||
href={`https://${process.env.NEXT_PUBLIC_EXPLORER_URL}/${submitRes.tx_json?.hash}`}
|
|
||||||
target="_blank"
|
|
||||||
rel="noopener noreferrer"
|
|
||||||
>
|
|
||||||
{submitRes.tx_json?.hash}
|
|
||||||
</Link>
|
|
||||||
</>
|
|
||||||
)
|
|
||||||
// message: `[${submitRes.engine_result}] ${submitRes.engine_result_message} Validated ledger index: ${submitRes.validated_ledger_index}`,
|
|
||||||
})
|
|
||||||
} else {
|
|
||||||
state.deployLogs.push({
|
|
||||||
type: 'error',
|
|
||||||
message: `[${submitRes.engine_result || submitRes.error}] ${
|
|
||||||
submitRes.engine_result_message || submitRes.error_exception
|
|
||||||
}`
|
|
||||||
})
|
|
||||||
}
|
|
||||||
} catch (err) {
|
|
||||||
console.log(err)
|
|
||||||
state.deployLogs.push({
|
state.deployLogs.push({
|
||||||
type: 'error',
|
type: 'error',
|
||||||
message: 'Error occurred while deploying'
|
message: resultMsg
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
state.deployLogs.push({
|
||||||
|
type: 'error',
|
||||||
|
message: `[${submitRes.error}] ${submitRes.error_exception}`
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
if (currentAccount) {
|
} catch (err) {
|
||||||
currentAccount.isLoading = false
|
console.error(err)
|
||||||
}
|
state.deployLogs.push({
|
||||||
return submitRes
|
type: 'error',
|
||||||
|
message: 'Error occurred while deploying'
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
if (currentAccount) {
|
||||||
|
currentAccount.isLoading = false
|
||||||
|
}
|
||||||
|
return submitRes
|
||||||
}
|
}
|
||||||
|
|
||||||
export const deleteHook = async (account: IAccount & { name?: string }) => {
|
export const deleteHook = async (account: IAccount & { name?: string }) => {
|
||||||
if (!state.client) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
const currentAccount = state.accounts.find(acc => acc.address === account.address)
|
const currentAccount = state.accounts.find(acc => acc.address === account.address)
|
||||||
if (currentAccount?.isLoading || !currentAccount?.hooks.length) {
|
if (currentAccount?.isLoading || !currentAccount?.hooks.length) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if (typeof window !== 'undefined') {
|
const tx = {
|
||||||
const tx = {
|
Account: account.address,
|
||||||
Account: account.address,
|
TransactionType: 'SetHook',
|
||||||
TransactionType: 'SetHook',
|
Sequence: account.sequence,
|
||||||
Sequence: account.sequence,
|
Fee: '100000',
|
||||||
Fee: '100000',
|
NetworkID: process.env.NEXT_PUBLIC_NETWORK_ID,
|
||||||
Hooks: [
|
Hooks: [
|
||||||
{
|
{
|
||||||
Hook: {
|
Hook: {
|
||||||
CreateCode: '',
|
CreateCode: '',
|
||||||
Flags: 1
|
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({
|
state.deployLogs.push({
|
||||||
type: 'error',
|
type: 'error',
|
||||||
message: 'Error occurred while deleting hook'
|
message: `[${submitRes.engine_result || submitRes.error}] ${
|
||||||
|
submitRes.engine_result_message || submitRes.error_exception
|
||||||
|
}`
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
if (currentAccount) {
|
} catch (err) {
|
||||||
currentAccount.isLoading = false
|
console.log(err)
|
||||||
}
|
toast.error('Error occurred while deleting hook', { id: toastId })
|
||||||
return submitRes
|
state.deployLogs.push({
|
||||||
|
type: 'error',
|
||||||
|
message: 'Error occurred while deleting hook'
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
if (currentAccount) {
|
||||||
|
currentAccount.isLoading = false
|
||||||
|
}
|
||||||
|
return submitRes
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -61,6 +61,7 @@ export const fetchFiles = async (gistId: string) => {
|
|||||||
// default priority is undefined == 0
|
// default priority is undefined == 0
|
||||||
const extPriority: Record<string, number> = {
|
const extPriority: Record<string, number> = {
|
||||||
c: 3,
|
c: 3,
|
||||||
|
wat: 3,
|
||||||
md: 2,
|
md: 2,
|
||||||
h: -1
|
h: -1
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,6 +2,9 @@ import { derive, sign } from 'xrpl-accountlib'
|
|||||||
|
|
||||||
import state from '..'
|
import state from '..'
|
||||||
import type { IAccount } from '..'
|
import type { IAccount } from '..'
|
||||||
|
import ResultLink from '../../components/ResultLink'
|
||||||
|
import { ref } from 'valtio'
|
||||||
|
import { xrplSend } from './xrpl-client'
|
||||||
|
|
||||||
interface TransactionOptions {
|
interface TransactionOptions {
|
||||||
TransactionType: string
|
TransactionType: string
|
||||||
@@ -19,34 +22,42 @@ export const sendTransaction = async (
|
|||||||
txOptions: TransactionOptions,
|
txOptions: TransactionOptions,
|
||||||
options?: OtherOptions
|
options?: OtherOptions
|
||||||
) => {
|
) => {
|
||||||
if (!state.client) throw Error('XRPL client not initalized')
|
|
||||||
|
|
||||||
const { Fee = '1000', ...opts } = txOptions
|
const { Fee = '1000', ...opts } = txOptions
|
||||||
const tx: TransactionOptions = {
|
const tx: TransactionOptions = {
|
||||||
Account: account.address,
|
Account: account.address,
|
||||||
Sequence: account.sequence,
|
Sequence: account.sequence,
|
||||||
Fee, // TODO auto-fillable default
|
Fee,
|
||||||
|
NetworkID: process.env.NEXT_PUBLIC_NETWORK_ID,
|
||||||
...opts
|
...opts
|
||||||
}
|
}
|
||||||
const { logPrefix = '' } = options || {}
|
const { logPrefix = '' } = options || {}
|
||||||
try {
|
try {
|
||||||
const signedAccount = derive.familySeed(account.secret)
|
const signedAccount = derive.familySeed(account.secret)
|
||||||
const { signedTransaction } = sign(tx, signedAccount)
|
const { signedTransaction } = sign(tx, signedAccount)
|
||||||
const response = await state.client.send({
|
const response = await xrplSend({
|
||||||
command: 'submit',
|
command: 'submit',
|
||||||
tx_blob: signedTransaction
|
tx_blob: signedTransaction
|
||||||
})
|
})
|
||||||
|
|
||||||
|
const resultMsg = ref(
|
||||||
|
<>
|
||||||
|
{logPrefix}[<ResultLink result={response.engine_result} />] {response.engine_result_message}
|
||||||
|
</>
|
||||||
|
)
|
||||||
if (response.engine_result === 'tesSUCCESS') {
|
if (response.engine_result === 'tesSUCCESS') {
|
||||||
state.transactionLogs.push({
|
state.transactionLogs.push({
|
||||||
type: 'success',
|
type: 'success',
|
||||||
message: `${logPrefix}[${response.engine_result}] ${response.engine_result_message}`
|
message: resultMsg
|
||||||
|
})
|
||||||
|
} else if (response.engine_result) {
|
||||||
|
state.transactionLogs.push({
|
||||||
|
type: 'error',
|
||||||
|
message: resultMsg
|
||||||
})
|
})
|
||||||
} else {
|
} else {
|
||||||
state.transactionLogs.push({
|
state.transactionLogs.push({
|
||||||
type: 'error',
|
type: 'error',
|
||||||
message: `${logPrefix}[${response.error || response.engine_result}] ${
|
message: `${logPrefix}[${response.error}] ${response.error_exception}`
|
||||||
response.error_exception || response.engine_result_message
|
|
||||||
}`
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
const currAcc = state.accounts.find(acc => acc.address === account.address)
|
const currAcc = state.accounts.find(acc => acc.address === account.address)
|
||||||
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);
|
||||||
|
}
|
||||||
79
state/constants/flags.ts
Normal file
79
state/constants/flags.ts
Normal file
@@ -0,0 +1,79 @@
|
|||||||
|
import { SelectOption } from '../transactions';
|
||||||
|
|
||||||
|
interface Flags {
|
||||||
|
[key: string]: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const transactionFlags: { [key: /* TransactionType */ string]: Flags } = {
|
||||||
|
"*": {
|
||||||
|
tfFullyCanonicalSig: '0x80000000'
|
||||||
|
},
|
||||||
|
Payment: {
|
||||||
|
tfNoDirectRipple: '0x00010000',
|
||||||
|
tfPartialPayment: '0x00020000',
|
||||||
|
tfLimitQuality: '0x00040000',
|
||||||
|
},
|
||||||
|
AccountSet: {
|
||||||
|
tfRequireDestTag: '0x00010000',
|
||||||
|
tfOptionalDestTag: '0x00020000',
|
||||||
|
tfRequireAuth: '0x00040000',
|
||||||
|
tfOptionalAuth: '0x00080000',
|
||||||
|
tfDisallowXRP: '0x00100000',
|
||||||
|
tfAllowXRP: '0x00200000',
|
||||||
|
},
|
||||||
|
NFTokenCreateOffer: {
|
||||||
|
tfSellNFToken: '0x00000001',
|
||||||
|
},
|
||||||
|
NFTokenMint: {
|
||||||
|
tfBurnable: '0x00000001',
|
||||||
|
tfOnlyXRP: '0x00000002',
|
||||||
|
tfTrustLine: '0x00000004',
|
||||||
|
tfTransferable: '0x00000008',
|
||||||
|
},
|
||||||
|
OfferCreate: {
|
||||||
|
tfPassive: '0x00010000',
|
||||||
|
tfImmediateOrCancel: '0x00020000',
|
||||||
|
tfFillOrKill: '0x00040000',
|
||||||
|
tfSell: '0x00080000',
|
||||||
|
},
|
||||||
|
PaymentChannelClaim: {
|
||||||
|
tfRenew: '0x00010000',
|
||||||
|
tfClose: '0x00020000',
|
||||||
|
},
|
||||||
|
TrustSet: {
|
||||||
|
tfSetfAuth: '0x00010000',
|
||||||
|
tfSetNoRipple: '0x00020000',
|
||||||
|
tfClearNoRipple: '0x00040000',
|
||||||
|
tfSetFreeze: '0x00100000',
|
||||||
|
tfClearFreeze: '0x00200000',
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
export const getFlags = (tt?: string) => {
|
||||||
|
if (!tt) return
|
||||||
|
const flags = {
|
||||||
|
...transactionFlags['*'],
|
||||||
|
...transactionFlags[tt]
|
||||||
|
}
|
||||||
|
|
||||||
|
return flags
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
export function combineFlags(flags?: string[]): string | undefined {
|
||||||
|
if (!flags) return
|
||||||
|
|
||||||
|
const num = flags.reduce((cumm, curr) => cumm | BigInt(curr), BigInt(0))
|
||||||
|
return num.toString()
|
||||||
|
}
|
||||||
|
|
||||||
|
export function extractFlags(transactionType: string, flags?: string | number,): SelectOption[] {
|
||||||
|
const flagsObj = getFlags(transactionType)
|
||||||
|
if (!flags || !flagsObj) return []
|
||||||
|
|
||||||
|
const extracted = Object.entries(flagsObj).reduce((cumm, [label, value]) => {
|
||||||
|
return (BigInt(flags) & BigInt(value)) ? cumm.concat({ label, value }) : cumm
|
||||||
|
}, [] as SelectOption[])
|
||||||
|
|
||||||
|
return extracted
|
||||||
|
}
|
||||||
@@ -78,7 +78,7 @@ export interface IState {
|
|||||||
splits: {
|
splits: {
|
||||||
[id: string]: SplitSize
|
[id: string]: SplitSize
|
||||||
}
|
}
|
||||||
client: XrplClient | null
|
client: XrplClient
|
||||||
clientStatus: 'offline' | 'online'
|
clientStatus: 'offline' | 'online'
|
||||||
mainModalOpen: boolean
|
mainModalOpen: boolean
|
||||||
mainModalShowed: boolean
|
mainModalShowed: boolean
|
||||||
@@ -113,7 +113,7 @@ let initialState: IState = {
|
|||||||
tabSize: 2
|
tabSize: 2
|
||||||
},
|
},
|
||||||
splits: {},
|
splits: {},
|
||||||
client: null,
|
client: undefined!, // set below only.
|
||||||
clientStatus: 'offline' as 'offline',
|
clientStatus: 'offline' as 'offline',
|
||||||
mainModalOpen: false,
|
mainModalOpen: false,
|
||||||
mainModalShowed: false,
|
mainModalShowed: false,
|
||||||
@@ -153,9 +153,9 @@ const state = proxy<IState>({
|
|||||||
})
|
})
|
||||||
// Initialize socket connection
|
// Initialize socket connection
|
||||||
const client = new XrplClient(`wss://${process.env.NEXT_PUBLIC_TESTNET_URL}`)
|
const client = new XrplClient(`wss://${process.env.NEXT_PUBLIC_TESTNET_URL}`)
|
||||||
|
state.client = ref(client);
|
||||||
|
|
||||||
client.on('online', () => {
|
client.on('online', () => {
|
||||||
state.client = ref(client)
|
|
||||||
state.clientStatus = 'online'
|
state.clientStatus = 'online'
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import transactionsData from '../content/transactions.json'
|
|||||||
import state from '.'
|
import state from '.'
|
||||||
import { showAlert } from '../state/actions/showAlert'
|
import { showAlert } from '../state/actions/showAlert'
|
||||||
import { parseJSON } from '../utils/json'
|
import { parseJSON } from '../utils/json'
|
||||||
|
import { extractFlags, getFlags } from './constants/flags'
|
||||||
|
|
||||||
export type SelectOption = {
|
export type SelectOption = {
|
||||||
value: string
|
value: string
|
||||||
@@ -14,6 +15,7 @@ export interface TransactionState {
|
|||||||
selectedTransaction: SelectOption | null
|
selectedTransaction: SelectOption | null
|
||||||
selectedAccount: SelectOption | null
|
selectedAccount: SelectOption | null
|
||||||
selectedDestAccount: SelectOption | null
|
selectedDestAccount: SelectOption | null
|
||||||
|
selectedFlags: SelectOption[] | null
|
||||||
txIsLoading: boolean
|
txIsLoading: boolean
|
||||||
txIsDisabled: boolean
|
txIsDisabled: boolean
|
||||||
txFields: TxFields
|
txFields: TxFields
|
||||||
@@ -31,6 +33,7 @@ export const defaultTransaction: TransactionState = {
|
|||||||
selectedTransaction: null,
|
selectedTransaction: null,
|
||||||
selectedAccount: null,
|
selectedAccount: null,
|
||||||
selectedDestAccount: null,
|
selectedDestAccount: null,
|
||||||
|
selectedFlags: null,
|
||||||
txIsLoading: false,
|
txIsLoading: false,
|
||||||
txIsDisabled: false,
|
txIsDisabled: false,
|
||||||
txFields: {},
|
txFields: {},
|
||||||
@@ -128,9 +131,8 @@ export const prepareTransaction = (data: any) => {
|
|||||||
try {
|
try {
|
||||||
options[field] = JSON.parse(_value.$value)
|
options[field] = JSON.parse(_value.$value)
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
const message = `Input error for json field '${field}': ${
|
const message = `Input error for json field '${field}': ${error instanceof Error ? error.message : ''
|
||||||
error instanceof Error ? error.message : ''
|
}`
|
||||||
}`
|
|
||||||
console.error(message)
|
console.error(message)
|
||||||
options[field] = _value.$value
|
options[field] = _value.$value
|
||||||
}
|
}
|
||||||
@@ -205,6 +207,13 @@ export const prepareState = (value: string, transactionType?: string) => {
|
|||||||
rest.Destination = 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 => {
|
Object.keys(rest).forEach(field => {
|
||||||
const value = rest[field]
|
const value = rest[field]
|
||||||
const schemaVal = schema[field as keyof TxFields]
|
const schemaVal = schema[field as keyof TxFields]
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import toast from 'react-hot-toast'
|
import toast from 'react-hot-toast'
|
||||||
import { derive, sign } from 'xrpl-accountlib'
|
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 (
|
const estimateFee = async (
|
||||||
tx: Record<string, unknown>,
|
tx: Record<string, unknown>,
|
||||||
@@ -22,7 +23,7 @@ const estimateFee = async (
|
|||||||
const keypair = derive.familySeed(account.secret)
|
const keypair = derive.familySeed(account.secret)
|
||||||
const { signedTransaction } = sign(copyTx, keypair)
|
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) {
|
if (res && res.drops) {
|
||||||
return res.drops
|
return res.drops
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -13,3 +13,9 @@ export const capitalize = (value?: string) => {
|
|||||||
|
|
||||||
return value[0].toLocaleUpperCase() + value.slice(1)
|
return value[0].toLocaleUpperCase() + value.slice(1)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export const getFileExtention = (filename?: string): string | undefined => {
|
||||||
|
if (!filename) return
|
||||||
|
const ext = (filename.includes('.') && filename.split('.').pop()) || undefined
|
||||||
|
return ext
|
||||||
|
}
|
||||||
|
|||||||
@@ -23,23 +23,22 @@ export const tts = {
|
|||||||
ttNFTOKEN_BURN: 26,
|
ttNFTOKEN_BURN: 26,
|
||||||
ttNFTOKEN_CREATE_OFFER: 27,
|
ttNFTOKEN_CREATE_OFFER: 27,
|
||||||
ttNFTOKEN_CANCEL_OFFER: 28,
|
ttNFTOKEN_CANCEL_OFFER: 28,
|
||||||
ttNFTOKEN_ACCEPT_OFFER: 29
|
ttNFTOKEN_ACCEPT_OFFER: 29,
|
||||||
|
ttINVOKE: 99,
|
||||||
}
|
}
|
||||||
|
|
||||||
export type TTS = typeof tts
|
export type TTS = typeof tts
|
||||||
|
|
||||||
const calculateHookOn = (arr: (keyof TTS)[]) => {
|
const calculateHookOn = (arr: (keyof TTS)[]) => {
|
||||||
let start = '0x000000003e3ff5bf'
|
let s = '0x3e3ff5bf'
|
||||||
arr.forEach(n => {
|
arr.forEach(n => {
|
||||||
let v = BigInt(start)
|
let v = BigInt(s)
|
||||||
v ^= BigInt(1) << BigInt(tts[n as keyof TTS])
|
v ^= BigInt(1) << BigInt(tts[n])
|
||||||
let s = v.toString(16)
|
s = "0x" + v.toString(16)
|
||||||
let l = s.length
|
|
||||||
if (l < 16) s = '0'.repeat(16 - l) + s
|
|
||||||
s = '0x' + s
|
|
||||||
start = s
|
|
||||||
})
|
})
|
||||||
return start.substring(2)
|
s = s.replace('0x', '')
|
||||||
|
s = s.padStart(64, '0')
|
||||||
|
return s
|
||||||
}
|
}
|
||||||
|
|
||||||
export default calculateHookOn
|
export default calculateHookOn
|
||||||
|
|||||||
File diff suppressed because one or more lines are too long
@@ -22,6 +22,7 @@ import hooksFloatManipPure from './md/hooks-float-manip-pure.md'
|
|||||||
import hooksFloatOnePure from './md/hooks-float-one-pure.md'
|
import hooksFloatOnePure from './md/hooks-float-one-pure.md'
|
||||||
import hooksFloatPure from './md/hooks-float-pure.md'
|
import hooksFloatPure from './md/hooks-float-pure.md'
|
||||||
import hooksGuardCalled from './md/hooks-guard-called.md'
|
import hooksGuardCalled from './md/hooks-guard-called.md'
|
||||||
|
import hooksGuardCallNonConst from './md/hooks-guard-call-non-const.md'
|
||||||
import hooksGuardInFor from './md/hooks-guard-in-for.md'
|
import hooksGuardInFor from './md/hooks-guard-in-for.md'
|
||||||
import hooksGuardInWhile from './md/hooks-guard-in-while.md'
|
import hooksGuardInWhile from './md/hooks-guard-in-while.md'
|
||||||
import hooksHashBufLen from './md/hooks-hash-buf-len.md'
|
import hooksHashBufLen from './md/hooks-hash-buf-len.md'
|
||||||
@@ -70,6 +71,7 @@ const docs: { [key: string]: string } = {
|
|||||||
'hooks-float-one-pure': hooksFloatOnePure,
|
'hooks-float-one-pure': hooksFloatOnePure,
|
||||||
'hooks-float-pure': hooksFloatPure,
|
'hooks-float-pure': hooksFloatPure,
|
||||||
'hooks-guard-called': hooksGuardCalled,
|
'hooks-guard-called': hooksGuardCalled,
|
||||||
|
'hooks-guard-call-non-const': hooksGuardCallNonConst,
|
||||||
'hooks-guard-in-for': hooksGuardInFor,
|
'hooks-guard-in-for': hooksGuardInFor,
|
||||||
'hooks-guard-in-while': hooksGuardInWhile,
|
'hooks-guard-in-while': hooksGuardInWhile,
|
||||||
'hooks-hash-buf-len': hooksHashBufLen,
|
'hooks-hash-buf-len': hooksHashBufLen,
|
||||||
|
|||||||
6
xrpl-hooks-docs/md/hooks-guard-call-non-const.md
Normal file
6
xrpl-hooks-docs/md/hooks-guard-call-non-const.md
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
# hooks-guard-call-non-const
|
||||||
|
|
||||||
|
Only compile-time constants can be used as an argument in loop GUARD call. This check warns if a non compile-time constant is used.
|
||||||
|
It also checks whether a compile-time constant is used as a first argument of `_g()` call and whether it is a unique value. If not - it warns.
|
||||||
|
|
||||||
|
[Read more](https://xrpl-hooks.readme.io/v2.0/docs/loops-and-guarding)
|
||||||
@@ -1,14 +1,35 @@
|
|||||||
# hooks-guard-in-for
|
# hooks-guard-in-for
|
||||||
|
|
||||||
A guard is a marker that must be placed in your code at the top of each loop. Consider the following for-loop in C:
|
Consider the following for-loop in C:
|
||||||
|
|
||||||
```c
|
```c
|
||||||
#define GUARD(maxiter) _g(__LINE__, (maxiter)+1)
|
#define GUARD(maxiter) _g(__LINE__, (maxiter)+1)
|
||||||
|
for (int i = 0; GUARD(3), i < 3; ++i)
|
||||||
for (int i = 0; GUARD(3), i < 3; ++i)
|
|
||||||
```
|
```
|
||||||
|
|
||||||
<BR/>
|
To satisfy the guard rule when using a for-loop in C guard should be
|
||||||
This is the only way to satisfy the guard rule when using a for-loop in C.
|
placed either in the condition part of the loop, or as a first call in loop body, e.g.
|
||||||
|
|
||||||
|
```c
|
||||||
|
for(int i = 0; i < 3; ++i) {
|
||||||
|
GUARD(3);
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
In case of nested loops, the guard limit value should be
|
||||||
|
multiplied by a number of iterations in each loop, e.g.
|
||||||
|
|
||||||
|
```c
|
||||||
|
for(int i = 0; GUARD(3), i < 3; ++i) {
|
||||||
|
for (int j = 0; GUARD(17), j < 5; ++j)
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
```
|
||||||
|
(most descendant loop iterations + 1) * (each parent loops iterations) - 1
|
||||||
|
```
|
||||||
|
|
||||||
|
This check will warn if the GUARD call is missing and also it will propose a GUARD value based on the for loop initial value,
|
||||||
|
the increment and loop condition.
|
||||||
|
|
||||||
[Read more](https://xrpl-hooks.readme.io/v2.0/docs/loops-and-guarding)
|
[Read more](https://xrpl-hooks.readme.io/v2.0/docs/loops-and-guarding)
|
||||||
|
|||||||
Reference in New Issue
Block a user