Compare commits
2 Commits
feat/accou
...
mobile-opt
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d3b0fef406 | ||
|
|
4cbc316c62 |
@@ -5,8 +5,7 @@ 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-v3.xrpl-labs.com"
|
NEXT_PUBLIC_TESTNET_URL="hooks-testnet-v2.xrpl-labs.com"
|
||||||
NEXT_PUBLIC_DEBUG_STREAM_URL="hooks-testnet-v3-debugstream.xrpl-labs.com"
|
NEXT_PUBLIC_DEBUG_STREAM_URL="hooks-testnet-v2-debugstream.xrpl-labs.com"
|
||||||
NEXT_PUBLIC_EXPLORER_URL="hooks-testnet-v3-explorer.xrpl-labs.com"
|
NEXT_PUBLIC_EXPLORER_URL="hooks-testnet-v2-explorer.xrpl-labs.com"
|
||||||
NEXT_PUBLIC_NETWORK_ID="21338"
|
NEXT_PUBLIC_SITE_URL=http://localhost:3000
|
||||||
NEXT_PUBLIC_SITE_URL="http://localhost:3000"
|
|
||||||
4
.gitignore
vendored
4
.gitignore
vendored
@@ -33,7 +33,3 @@ yarn-error.log*
|
|||||||
# vercel
|
# vercel
|
||||||
.vercel
|
.vercel
|
||||||
.vscode
|
.vscode
|
||||||
|
|
||||||
# yarn
|
|
||||||
.yarnrc.yml
|
|
||||||
.yarn/
|
|
||||||
|
|||||||
@@ -33,7 +33,6 @@ 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,
|
||||||
@@ -302,7 +301,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 =>
|
||||||
xrplSend({
|
snap.client?.send({
|
||||||
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
|
||||||
@@ -330,7 +329,7 @@ const Accounts: FC<AccountProps> = props => {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
const objectRequests = snap.accounts.map(acc => {
|
const objectRequests = snap.accounts.map(acc => {
|
||||||
return xrplSend({
|
return snap.client?.send({
|
||||||
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
|
||||||
|
|||||||
@@ -6,11 +6,14 @@ const ButtonGroup = styled('div', {
|
|||||||
marginLeft: '1px',
|
marginLeft: '1px',
|
||||||
[`& ${StyledButton}`]: {
|
[`& ${StyledButton}`]: {
|
||||||
marginLeft: '-1px',
|
marginLeft: '-1px',
|
||||||
px: '$4',
|
px: '0.65rem',
|
||||||
zIndex: 2,
|
zIndex: 2,
|
||||||
position: 'relative',
|
position: 'relative',
|
||||||
'&:hover, &:focus': {
|
'&:hover, &:focus': {
|
||||||
zIndex: 200
|
zIndex: 200
|
||||||
|
},
|
||||||
|
'@sm': {
|
||||||
|
px: '$4'
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
[`& ${StyledButton}:not(:only-of-type):not(:first-child):not(:last-child)`]: {
|
[`& ${StyledButton}:not(:only-of-type):not(:first-child):not(:last-child)`]: {
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ const contentShow = keyframes({
|
|||||||
'100%': { opacity: 1 }
|
'100%': { opacity: 1 }
|
||||||
})
|
})
|
||||||
const StyledOverlay = styled(DialogPrimitive.Overlay, {
|
const StyledOverlay = styled(DialogPrimitive.Overlay, {
|
||||||
zIndex: 3000,
|
zIndex: 10000,
|
||||||
backgroundColor: blackA.blackA9,
|
backgroundColor: blackA.blackA9,
|
||||||
position: 'fixed',
|
position: 'fixed',
|
||||||
inset: 0,
|
inset: 0,
|
||||||
@@ -40,11 +40,10 @@ const StyledContent = styled(DialogPrimitive.Content, {
|
|||||||
color: '$mauve12',
|
color: '$mauve12',
|
||||||
borderRadius: '$md',
|
borderRadius: '$md',
|
||||||
position: 'relative',
|
position: 'relative',
|
||||||
mb: '15%',
|
mb: '0%',
|
||||||
boxShadow: '0px 10px 38px -5px rgba(22, 23, 24, 0.25), 0px 10px 20px -5px rgba(22, 23, 24, 0.2)',
|
boxShadow: '0px 10px 38px -5px rgba(22, 23, 24, 0.25), 0px 10px 20px -5px rgba(22, 23, 24, 0.2)',
|
||||||
width: '90vw',
|
width: '90vw',
|
||||||
maxWidth: '450px',
|
maxWidth: '450px',
|
||||||
// maxHeight: "85vh",
|
|
||||||
padding: 25,
|
padding: 25,
|
||||||
'@media (prefers-reduced-motion: no-preference)': {
|
'@media (prefers-reduced-motion: no-preference)': {
|
||||||
animation: `${contentShow} 150ms cubic-bezier(0.16, 1, 0.3, 1)`
|
animation: `${contentShow} 150ms cubic-bezier(0.16, 1, 0.3, 1)`
|
||||||
@@ -53,6 +52,9 @@ const StyledContent = styled(DialogPrimitive.Content, {
|
|||||||
'.dark &': {
|
'.dark &': {
|
||||||
backgroundColor: '$mauve5',
|
backgroundColor: '$mauve5',
|
||||||
boxShadow: '0px 10px 38px 0px rgba(0, 0, 0, 0.85), 0px 10px 20px 0px rgba(0, 0, 0, 0.6)'
|
boxShadow: '0px 10px 38px 0px rgba(0, 0, 0, 0.85), 0px 10px 20px 0px rgba(0, 0, 0, 0.6)'
|
||||||
|
},
|
||||||
|
'@md': {
|
||||||
|
mb: '8%'
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|||||||
@@ -78,7 +78,6 @@ const EditorNavigation = ({ renderNav }: { renderNav?: () => ReactNode }) => {
|
|||||||
return (
|
return (
|
||||||
<Flex css={{ flexShrink: 0, gap: '$0' }}>
|
<Flex css={{ flexShrink: 0, gap: '$0' }}>
|
||||||
<Flex
|
<Flex
|
||||||
id="kissa"
|
|
||||||
ref={scrollRef}
|
ref={scrollRef}
|
||||||
css={{
|
css={{
|
||||||
overflowX: 'scroll',
|
overflowX: 'scroll',
|
||||||
@@ -181,7 +180,21 @@ const EditorNavigation = ({ renderNav }: { renderNav?: () => ReactNode }) => {
|
|||||||
</DropdownMenuContent>
|
</DropdownMenuContent>
|
||||||
</DropdownMenu>
|
</DropdownMenu>
|
||||||
) : (
|
) : (
|
||||||
<Button outline size="sm" css={{ mr: '$3' }} onClick={() => setPopUp(true)}>
|
<Button
|
||||||
|
outline
|
||||||
|
size="sm"
|
||||||
|
css={{
|
||||||
|
mr: '-1px',
|
||||||
|
borderTopRightRadius: 0,
|
||||||
|
borderBottomRightRadius: 0,
|
||||||
|
'@sm': {
|
||||||
|
borderTopRightRadius: '$sm',
|
||||||
|
borderBottomRightRadius: '$sm',
|
||||||
|
mr: '$3'
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
onClick={() => setPopUp(true)}
|
||||||
|
>
|
||||||
<GithubLogo size="16px" /> Login
|
<GithubLogo size="16px" /> Login
|
||||||
</Button>
|
</Button>
|
||||||
)}
|
)}
|
||||||
@@ -202,6 +215,9 @@ const EditorNavigation = ({ renderNav }: { renderNav?: () => ReactNode }) => {
|
|||||||
alignSelf: 'flex-start',
|
alignSelf: 'flex-start',
|
||||||
boxShadow: 'none'
|
boxShadow: 'none'
|
||||||
},
|
},
|
||||||
|
'button:not(:last-child)': {
|
||||||
|
display: 'none'
|
||||||
|
},
|
||||||
'button:not(:first-child):not(:last-child)': {
|
'button:not(:first-child):not(:last-child)': {
|
||||||
borderRight: 0,
|
borderRight: 0,
|
||||||
borderLeft: 0
|
borderLeft: 0
|
||||||
@@ -217,6 +233,11 @@ const EditorNavigation = ({ renderNav }: { renderNav?: () => ReactNode }) => {
|
|||||||
'&:hover': {
|
'&:hover': {
|
||||||
boxShadow: 'inset 0px 0px 0px 1px $colors$mauve12'
|
boxShadow: 'inset 0px 0px 0px 1px $colors$mauve12'
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
'@sm': {
|
||||||
|
'button:not(:last-child)': {
|
||||||
|
display: 'flex'
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
|
|||||||
@@ -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,11 +218,6 @@ 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 || ''
|
||||||
|
|||||||
@@ -33,7 +33,7 @@ import { styled } from '../stitches.config'
|
|||||||
const ImageWrapper = styled(Flex, {
|
const ImageWrapper = styled(Flex, {
|
||||||
position: 'relative',
|
position: 'relative',
|
||||||
mt: '$2',
|
mt: '$2',
|
||||||
mb: '$10',
|
mb: '$6',
|
||||||
svg: {
|
svg: {
|
||||||
// fill: "red",
|
// fill: "red",
|
||||||
'.angle': {
|
'.angle': {
|
||||||
@@ -66,16 +66,23 @@ const Navigation = () => {
|
|||||||
<Container
|
<Container
|
||||||
css={{
|
css={{
|
||||||
display: 'flex',
|
display: 'flex',
|
||||||
alignItems: 'center'
|
alignItems: 'center',
|
||||||
|
px: '$3',
|
||||||
|
'@sm': {
|
||||||
|
px: '$4'
|
||||||
|
}
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Flex
|
<Flex
|
||||||
css={{
|
css={{
|
||||||
flex: 1,
|
flex: 1,
|
||||||
alignItems: 'center',
|
alignItems: 'center',
|
||||||
borderRight: '1px solid $colors$mauve6',
|
|
||||||
py: '$3',
|
py: '$3',
|
||||||
pr: '$4'
|
pr: '$0',
|
||||||
|
'@sm': {
|
||||||
|
borderRight: '1px solid $colors$mauve6',
|
||||||
|
pr: '$4'
|
||||||
|
}
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Link href={gistId ? `/develop/${gistId}` : '/develop'} passHref>
|
<Link href={gistId ? `/develop/${gistId}` : '/develop'} passHref>
|
||||||
@@ -84,7 +91,11 @@ const Navigation = () => {
|
|||||||
css={{
|
css={{
|
||||||
display: 'flex',
|
display: 'flex',
|
||||||
alignItems: 'center',
|
alignItems: 'center',
|
||||||
color: '$textColor'
|
color: '$textColor',
|
||||||
|
mr: '$2',
|
||||||
|
'@sm': {
|
||||||
|
mr: '$4'
|
||||||
|
}
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Logo width="32px" height="32px" />
|
<Logo width="32px" height="32px" />
|
||||||
@@ -92,9 +103,12 @@ const Navigation = () => {
|
|||||||
</Link>
|
</Link>
|
||||||
<Flex
|
<Flex
|
||||||
css={{
|
css={{
|
||||||
ml: '$5',
|
|
||||||
flexDirection: 'column',
|
flexDirection: 'column',
|
||||||
gap: '1px'
|
gap: '1px',
|
||||||
|
display: 'none',
|
||||||
|
'@md': {
|
||||||
|
display: 'flex'
|
||||||
|
}
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{snap.loading ? (
|
{snap.loading ? (
|
||||||
@@ -135,8 +149,8 @@ const Navigation = () => {
|
|||||||
css={{
|
css={{
|
||||||
display: 'flex',
|
display: 'flex',
|
||||||
maxWidth: '1080px',
|
maxWidth: '1080px',
|
||||||
width: '80vw',
|
width: '90vw',
|
||||||
maxHeight: '80%',
|
maxHeight: '90%',
|
||||||
backgroundColor: '$mauve1 !important',
|
backgroundColor: '$mauve1 !important',
|
||||||
overflowY: 'auto',
|
overflowY: 'auto',
|
||||||
background: 'black',
|
background: 'black',
|
||||||
@@ -265,15 +279,18 @@ const Navigation = () => {
|
|||||||
gridTemplateColumns: '1fr',
|
gridTemplateColumns: '1fr',
|
||||||
gridTemplateRows: 'max-content',
|
gridTemplateRows: 'max-content',
|
||||||
flex: 1,
|
flex: 1,
|
||||||
p: '$7',
|
p: '$4',
|
||||||
pb: '$16',
|
pb: '$8',
|
||||||
gap: '$3',
|
gap: '$3',
|
||||||
alignItems: 'normal',
|
alignItems: 'normal',
|
||||||
flexWrap: 'wrap',
|
flexWrap: 'wrap',
|
||||||
backgroundColor: '$mauve1',
|
backgroundColor: '$mauve1',
|
||||||
'@md': {
|
'@md': {
|
||||||
gridTemplateColumns: '1fr 1fr',
|
gridTemplateColumns: '1fr 1fr',
|
||||||
gridTemplateRows: 'max-content'
|
gridTemplateRows: 'max-content',
|
||||||
|
p: '$7',
|
||||||
|
pb: '$10',
|
||||||
|
paddingRight: '$12'
|
||||||
},
|
},
|
||||||
'@lg': {
|
'@lg': {
|
||||||
gridTemplateColumns: '1fr 1fr 1fr',
|
gridTemplateColumns: '1fr 1fr 1fr',
|
||||||
@@ -316,23 +333,30 @@ const Navigation = () => {
|
|||||||
<Flex
|
<Flex
|
||||||
css={{
|
css={{
|
||||||
flexWrap: 'nowrap',
|
flexWrap: 'nowrap',
|
||||||
marginLeft: '$4',
|
marginLeft: '$2',
|
||||||
overflowX: 'scroll',
|
overflowX: 'scroll',
|
||||||
'&::-webkit-scrollbar': {
|
'&::-webkit-scrollbar': {
|
||||||
height: 0,
|
height: 0,
|
||||||
background: 'transparent'
|
background: 'transparent'
|
||||||
},
|
},
|
||||||
scrollbarColor: 'transparent',
|
scrollbarColor: 'transparent',
|
||||||
scrollbarWidth: 'none'
|
scrollbarWidth: 'none',
|
||||||
|
'@sm': {
|
||||||
|
marginLeft: '$4'
|
||||||
|
}
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Stack
|
<Stack
|
||||||
css={{
|
css={{
|
||||||
ml: '$4',
|
ml: '$0',
|
||||||
gap: '$3',
|
gap: '$2',
|
||||||
flexWrap: 'nowrap',
|
flexWrap: 'nowrap',
|
||||||
alignItems: 'center',
|
alignItems: 'center',
|
||||||
marginLeft: 'auto'
|
marginLeft: 'auto',
|
||||||
|
'@sm': {
|
||||||
|
marginLeft: '$4',
|
||||||
|
gap: '$3'
|
||||||
|
}
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<ButtonGroup>
|
<ButtonGroup>
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ const PanelBox = styled('div', {
|
|||||||
flexDirection: 'column',
|
flexDirection: 'column',
|
||||||
border: '1px solid $colors$mauve6',
|
border: '1px solid $colors$mauve6',
|
||||||
backgroundColor: '$mauve2',
|
backgroundColor: '$mauve2',
|
||||||
padding: '$3',
|
padding: '$5',
|
||||||
borderRadius: '$sm',
|
borderRadius: '$sm',
|
||||||
fontWeight: 'lighter',
|
fontWeight: 'lighter',
|
||||||
height: 'auto',
|
height: 'auto',
|
||||||
|
|||||||
@@ -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 = async (code: string, data?: Record<string, any>) => {
|
const generateHtmlTemplate = (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,10 +29,8 @@ const generateHtmlTemplate = async (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>
|
||||||
@@ -74,9 +72,7 @@ const generateHtmlTemplate = async (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>
|
||||||
@@ -104,7 +100,6 @@ 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']
|
||||||
@@ -132,31 +127,16 @@ const RunScript: React.FC<{ file: IFile }> = ({ file: { content, name } }) => {
|
|||||||
return fields
|
return fields
|
||||||
}, [content])
|
}, [content])
|
||||||
|
|
||||||
const runScript = useCallback(async () => {
|
const runScript = useCallback(() => {
|
||||||
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 = await generateHtmlTemplate(content, data)
|
const template = 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 = [
|
||||||
@@ -165,7 +145,6 @@ 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(() => {
|
||||||
@@ -195,11 +174,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(async () => {
|
const handleRun = useCallback(() => {
|
||||||
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 = []
|
||||||
await runScript();
|
runScript()
|
||||||
setIsDialogOpen(false)
|
setIsDialogOpen(false)
|
||||||
}, [isDisabled, runScript])
|
}, [isDisabled, runScript])
|
||||||
|
|
||||||
@@ -300,7 +279,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 || isLoading} isLoading={isLoading} onClick={handleRun}>
|
<Button variant="primary" isDisabled={isDisabled} onClick={handleRun}>
|
||||||
Run script
|
Run script
|
||||||
</Button>
|
</Button>
|
||||||
</Flex>
|
</Flex>
|
||||||
|
|||||||
@@ -3,11 +3,13 @@ import { mauve, mauveDark, purple, purpleDark } from '@radix-ui/colors'
|
|||||||
import { useTheme } from 'next-themes'
|
import { useTheme } from 'next-themes'
|
||||||
import { styled } from '../stitches.config'
|
import { styled } from '../stitches.config'
|
||||||
import dynamic from 'next/dynamic'
|
import dynamic from 'next/dynamic'
|
||||||
import type { Props, StylesConfig } from 'react-select'
|
import type { Props } from 'react-select'
|
||||||
const SelectInput = dynamic(() => import('react-select'), { ssr: false })
|
const SelectInput = dynamic(() => import('react-select'), { ssr: false })
|
||||||
const CreatableSelectInput = dynamic(() => import('react-select/creatable'), { ssr: false })
|
|
||||||
|
|
||||||
const getColors = (isDark: boolean) => {
|
// eslint-disable-next-line react/display-name
|
||||||
|
const Select = forwardRef<any, Props>((props, ref) => {
|
||||||
|
const { theme } = useTheme()
|
||||||
|
const isDark = theme === 'dark'
|
||||||
const colors: any = {
|
const colors: any = {
|
||||||
// primary: pink.pink9,
|
// primary: pink.pink9,
|
||||||
active: isDark ? purpleDark.purple9 : purple.purple9,
|
active: isDark ? purpleDark.purple9 : purple.purple9,
|
||||||
@@ -28,136 +30,93 @@ const getColors = (isDark: boolean) => {
|
|||||||
}
|
}
|
||||||
colors.outline = colors.background
|
colors.outline = colors.background
|
||||||
colors.selected = colors.secondary
|
colors.selected = colors.secondary
|
||||||
return colors
|
|
||||||
}
|
|
||||||
|
|
||||||
const getStyles = (isDark: boolean) => {
|
|
||||||
const colors = getColors(isDark)
|
|
||||||
const styles: StylesConfig = {
|
|
||||||
container: provided => {
|
|
||||||
return {
|
|
||||||
...provided,
|
|
||||||
position: 'relative',
|
|
||||||
width: '100%'
|
|
||||||
}
|
|
||||||
},
|
|
||||||
singleValue: provided => ({
|
|
||||||
...provided,
|
|
||||||
color: colors.mauve12
|
|
||||||
}),
|
|
||||||
menu: provided => ({
|
|
||||||
...provided,
|
|
||||||
backgroundColor: colors.dropDownBg
|
|
||||||
}),
|
|
||||||
control: (provided, state) => {
|
|
||||||
return {
|
|
||||||
...provided,
|
|
||||||
minHeight: 0,
|
|
||||||
border: '0px',
|
|
||||||
backgroundColor: colors.mauve4,
|
|
||||||
boxShadow: `0 0 0 1px ${state.isFocused ? colors.border : colors.secondary}`
|
|
||||||
}
|
|
||||||
},
|
|
||||||
input: provided => {
|
|
||||||
return {
|
|
||||||
...provided,
|
|
||||||
color: '$text'
|
|
||||||
}
|
|
||||||
},
|
|
||||||
multiValue: provided => {
|
|
||||||
return {
|
|
||||||
...provided,
|
|
||||||
backgroundColor: colors.mauve8
|
|
||||||
}
|
|
||||||
},
|
|
||||||
multiValueLabel: provided => {
|
|
||||||
return {
|
|
||||||
...provided,
|
|
||||||
color: colors.mauve12
|
|
||||||
}
|
|
||||||
},
|
|
||||||
multiValueRemove: provided => {
|
|
||||||
return {
|
|
||||||
...provided,
|
|
||||||
':hover': {
|
|
||||||
background: colors.mauve9
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
option: (provided, state) => {
|
|
||||||
return {
|
|
||||||
...provided,
|
|
||||||
color: colors.searchText,
|
|
||||||
backgroundColor: state.isFocused ? colors.activeLight : colors.dropDownBg,
|
|
||||||
':hover': {
|
|
||||||
backgroundColor: colors.active,
|
|
||||||
color: '#ffffff'
|
|
||||||
},
|
|
||||||
':selected': {
|
|
||||||
backgroundColor: 'red'
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
indicatorSeparator: provided => {
|
|
||||||
return {
|
|
||||||
...provided,
|
|
||||||
backgroundColor: colors.secondary
|
|
||||||
}
|
|
||||||
},
|
|
||||||
dropdownIndicator: (provided, state) => {
|
|
||||||
return {
|
|
||||||
...provided,
|
|
||||||
padding: 6,
|
|
||||||
color: state.isFocused ? colors.border : colors.secondary,
|
|
||||||
':hover': {
|
|
||||||
color: colors.border
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
clearIndicator: provided => {
|
|
||||||
return {
|
|
||||||
...provided,
|
|
||||||
padding: 6,
|
|
||||||
color: colors.secondary,
|
|
||||||
':hover': {
|
|
||||||
color: colors.border
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return styles
|
|
||||||
}
|
|
||||||
|
|
||||||
// eslint-disable-next-line react/display-name
|
|
||||||
const Select = forwardRef<any, Props>((props, ref) => {
|
|
||||||
const { theme } = useTheme()
|
|
||||||
const isDark = theme === 'dark'
|
|
||||||
const styles = getStyles(isDark)
|
|
||||||
return (
|
return (
|
||||||
<SelectInput
|
<SelectInput
|
||||||
ref={ref}
|
ref={ref}
|
||||||
menuPosition={props.menuPosition || 'fixed'}
|
menuPosition={props.menuPosition || 'fixed'}
|
||||||
styles={styles}
|
styles={{
|
||||||
{...props}
|
container: provided => {
|
||||||
/>
|
return {
|
||||||
)
|
...provided,
|
||||||
})
|
position: 'relative'
|
||||||
|
}
|
||||||
// eslint-disable-next-line react/display-name
|
},
|
||||||
const Creatable = forwardRef<any, Props>((props, ref) => {
|
singleValue: provided => ({
|
||||||
const { theme } = useTheme()
|
...provided,
|
||||||
const isDark = theme === 'dark'
|
color: colors.mauve12
|
||||||
const styles = getStyles(isDark)
|
}),
|
||||||
return (
|
menu: provided => ({
|
||||||
<CreatableSelectInput
|
...provided,
|
||||||
ref={ref}
|
backgroundColor: colors.dropDownBg
|
||||||
formatCreateLabel={label => `Enter "${label}"`}
|
}),
|
||||||
menuPosition={props.menuPosition || 'fixed'}
|
control: (provided, state) => {
|
||||||
styles={styles}
|
return {
|
||||||
|
...provided,
|
||||||
|
minHeight: 0,
|
||||||
|
border: '0px',
|
||||||
|
backgroundColor: colors.mauve4,
|
||||||
|
boxShadow: `0 0 0 1px ${state.isFocused ? colors.border : colors.secondary}`
|
||||||
|
}
|
||||||
|
},
|
||||||
|
input: provided => {
|
||||||
|
return {
|
||||||
|
...provided,
|
||||||
|
color: '$text'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
multiValue: provided => {
|
||||||
|
return {
|
||||||
|
...provided,
|
||||||
|
backgroundColor: colors.mauve8
|
||||||
|
}
|
||||||
|
},
|
||||||
|
multiValueLabel: provided => {
|
||||||
|
return {
|
||||||
|
...provided,
|
||||||
|
color: colors.mauve12
|
||||||
|
}
|
||||||
|
},
|
||||||
|
multiValueRemove: provided => {
|
||||||
|
return {
|
||||||
|
...provided,
|
||||||
|
':hover': {
|
||||||
|
background: colors.mauve9
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
option: (provided, state) => {
|
||||||
|
return {
|
||||||
|
...provided,
|
||||||
|
color: colors.searchText,
|
||||||
|
backgroundColor: state.isFocused ? colors.activeLight : colors.dropDownBg,
|
||||||
|
':hover': {
|
||||||
|
backgroundColor: colors.active,
|
||||||
|
color: '#ffffff'
|
||||||
|
},
|
||||||
|
':selected': {
|
||||||
|
backgroundColor: 'red'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
indicatorSeparator: provided => {
|
||||||
|
return {
|
||||||
|
...provided,
|
||||||
|
backgroundColor: colors.secondary
|
||||||
|
}
|
||||||
|
},
|
||||||
|
dropdownIndicator: (provided, state) => {
|
||||||
|
return {
|
||||||
|
...provided,
|
||||||
|
color: state.isFocused ? colors.border : colors.secondary,
|
||||||
|
':hover': {
|
||||||
|
color: colors.border
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}}
|
||||||
{...props}
|
{...props}
|
||||||
/>
|
/>
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
export default styled(Select, {})
|
export default styled(Select, {})
|
||||||
export const CreatableSelect = styled(Creatable, {})
|
|
||||||
|
|||||||
@@ -1,71 +0,0 @@
|
|||||||
import { FC, useCallback, useState } from 'react'
|
|
||||||
import state from '../state'
|
|
||||||
import { Flex, Input, Button } from '.'
|
|
||||||
import fetchAccountInfo from '../utils/accountInfo'
|
|
||||||
import { useSnapshot } from 'valtio'
|
|
||||||
|
|
||||||
interface AccountSequenceProps {
|
|
||||||
address?: string
|
|
||||||
}
|
|
||||||
const AccountSequence: FC<AccountSequenceProps> = ({ address }) => {
|
|
||||||
const { accounts } = useSnapshot(state)
|
|
||||||
const account = accounts.find(acc => acc.address === address)
|
|
||||||
const [isLoading, setIsLoading] = useState(false)
|
|
||||||
const setSequence = useCallback(
|
|
||||||
(sequence: number) => {
|
|
||||||
const acc = state.accounts.find(acc => acc.address == address)
|
|
||||||
if (!acc) return
|
|
||||||
acc.sequence = sequence
|
|
||||||
},
|
|
||||||
[address]
|
|
||||||
)
|
|
||||||
const handleUpdateSequence = useCallback(
|
|
||||||
async (silent?: boolean) => {
|
|
||||||
if (!account) return
|
|
||||||
setIsLoading(true)
|
|
||||||
|
|
||||||
const info = await fetchAccountInfo(account.address, { silent })
|
|
||||||
if (info) {
|
|
||||||
setSequence(info.Sequence)
|
|
||||||
}
|
|
||||||
|
|
||||||
setIsLoading(false)
|
|
||||||
},
|
|
||||||
[account, setSequence]
|
|
||||||
)
|
|
||||||
const disabled = !account
|
|
||||||
return (
|
|
||||||
<Flex row align="center" fluid>
|
|
||||||
<Input
|
|
||||||
placeholder="Account sequence"
|
|
||||||
value={account?.sequence || ""}
|
|
||||||
disabled={!account}
|
|
||||||
type="number"
|
|
||||||
readOnly={true}
|
|
||||||
/>
|
|
||||||
<Button
|
|
||||||
size="xs"
|
|
||||||
variant="primary"
|
|
||||||
type="button"
|
|
||||||
outline
|
|
||||||
disabled={disabled}
|
|
||||||
isDisabled={disabled}
|
|
||||||
isLoading={isLoading}
|
|
||||||
css={{
|
|
||||||
background: '$backgroundAlt',
|
|
||||||
position: 'absolute',
|
|
||||||
right: '$2',
|
|
||||||
fontSize: '$xs',
|
|
||||||
cursor: 'pointer',
|
|
||||||
alignContent: 'center',
|
|
||||||
display: 'flex'
|
|
||||||
}}
|
|
||||||
onClick={() => handleUpdateSequence()}
|
|
||||||
>
|
|
||||||
Update
|
|
||||||
</Button>
|
|
||||||
</Flex>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
export default AccountSequence
|
|
||||||
@@ -21,7 +21,6 @@ import { prepareDeployHookTx, sha256 } from '../state/actions/deployHook'
|
|||||||
import estimateFee from '../utils/estimateFee'
|
import estimateFee from '../utils/estimateFee'
|
||||||
import { getParameters, getInvokeOptions, transactionOptions, SetHookData } from '../utils/setHook'
|
import { getParameters, getInvokeOptions, transactionOptions, SetHookData } from '../utils/setHook'
|
||||||
import { capitalize } from '../utils/helpers'
|
import { capitalize } from '../utils/helpers'
|
||||||
import AccountSequence from './Sequence'
|
|
||||||
|
|
||||||
export const SetHookDialog: React.FC<{ accountAddress: string }> = React.memo(
|
export const SetHookDialog: React.FC<{ accountAddress: string }> = React.memo(
|
||||||
({ accountAddress }) => {
|
({ accountAddress }) => {
|
||||||
@@ -191,10 +190,6 @@ export const SetHookDialog: React.FC<{ accountAddress: string }> = React.memo(
|
|||||||
onChange={(acc: any) => setSelectedAccount(acc)}
|
onChange={(acc: any) => setSelectedAccount(acc)}
|
||||||
/>
|
/>
|
||||||
</Box>
|
</Box>
|
||||||
<Box css={{ width: '100%', position: 'relative' }}>
|
|
||||||
<Label>Sequence</Label>
|
|
||||||
<AccountSequence address={selectedAccount?.value} />
|
|
||||||
</Box>
|
|
||||||
<Box css={{ width: '100%' }}>
|
<Box css={{ width: '100%' }}>
|
||||||
<Label>Invoke on transactions</Label>
|
<Label>Invoke on transactions</Label>
|
||||||
<Controller
|
<Controller
|
||||||
|
|||||||
@@ -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, getFileExtention } from '../utils/helpers'
|
import { capitalize } 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 = getFileExtention(tabname)
|
let ext = (tabname.includes('.') && tabname.split('.').pop()) || ''
|
||||||
|
|
||||||
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 && ext && !allowedExtensions.includes(ext)) {
|
if (allowedExtensions && !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)) {
|
||||||
|
|||||||
@@ -19,8 +19,6 @@ 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'
|
|
||||||
import { SetHookData, toHex } from '../../utils/setHook'
|
|
||||||
|
|
||||||
export interface TransactionProps {
|
export interface TransactionProps {
|
||||||
header: string
|
header: string
|
||||||
@@ -41,40 +39,17 @@ const Transaction: FC<TransactionProps> = ({ header, state: txState, ...props })
|
|||||||
|
|
||||||
const prepareOptions = useCallback(
|
const prepareOptions = useCallback(
|
||||||
(state: Partial<TransactionState> = txState) => {
|
(state: Partial<TransactionState> = txState) => {
|
||||||
const {
|
const { selectedTransaction, selectedDestAccount, selectedAccount, txFields } = state
|
||||||
selectedTransaction,
|
|
||||||
selectedAccount,
|
|
||||||
txFields,
|
|
||||||
selectedFlags,
|
|
||||||
hookParameters,
|
|
||||||
memos
|
|
||||||
} = state
|
|
||||||
|
|
||||||
const TransactionType = selectedTransaction?.value || null
|
const TransactionType = selectedTransaction?.value || null
|
||||||
|
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
|
|
||||||
const HookParameters = Object.entries(hookParameters || {}).reduce<
|
|
||||||
SetHookData['HookParameters']
|
|
||||||
>((acc, [_, { label, value }]) => {
|
|
||||||
return acc.concat({
|
|
||||||
HookParameter: { HookParameterName: toHex(label), HookParameterValue: toHex(value) }
|
|
||||||
})
|
|
||||||
}, [])
|
|
||||||
const Memos = memos
|
|
||||||
? Object.entries(memos).reduce<SetHookData['Memos']>((acc, [_, { format, data, type }]) => {
|
|
||||||
return acc?.concat({
|
|
||||||
Memo: { MemoData: toHex(data), MemoFormat: toHex(format), MemoType: toHex(type) }
|
|
||||||
})
|
|
||||||
}, [])
|
|
||||||
: undefined
|
|
||||||
|
|
||||||
return prepareTransaction({
|
return prepareTransaction({
|
||||||
...txFields,
|
...txFields,
|
||||||
HookParameters,
|
|
||||||
Flags,
|
|
||||||
TransactionType,
|
TransactionType,
|
||||||
Account,
|
Destination,
|
||||||
Memos
|
Account
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
[txState]
|
[txState]
|
||||||
@@ -90,29 +65,15 @@ const Transaction: FC<TransactionProps> = ({ header, state: txState, ...props })
|
|||||||
}
|
}
|
||||||
}, [selectedAccount?.value, selectedTransaction?.value, setState, txIsLoading])
|
}, [selectedAccount?.value, selectedTransaction?.value, setState, txIsLoading])
|
||||||
|
|
||||||
const getJsonString = useCallback(
|
|
||||||
(state?: Partial<TransactionState>) =>
|
|
||||||
JSON.stringify(prepareOptions?.(state) || {}, null, editorSettings.tabSize),
|
|
||||||
[editorSettings.tabSize, prepareOptions]
|
|
||||||
)
|
|
||||||
|
|
||||||
const saveEditorState = useCallback(
|
|
||||||
(value: string = '', transactionType?: string) => {
|
|
||||||
const pTx = prepareState(value, transactionType)
|
|
||||||
if (pTx) {
|
|
||||||
pTx.editorValue = getJsonString(pTx)
|
|
||||||
return setState(pTx)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
[getJsonString, setState]
|
|
||||||
)
|
|
||||||
|
|
||||||
const submitTest = useCallback(async () => {
|
const submitTest = useCallback(async () => {
|
||||||
let st: TransactionState | undefined
|
let st: TransactionState | undefined
|
||||||
const tt = txState.selectedTransaction?.value
|
const tt = txState.selectedTransaction?.value
|
||||||
if (viewType === 'json') {
|
if (viewType === 'json') {
|
||||||
st = saveEditorState(editorValue, tt)
|
// save the editor state first
|
||||||
if (!st) return
|
const pst = prepareState(editorValue || '', tt)
|
||||||
|
if (!pst) return
|
||||||
|
|
||||||
|
st = setState(pst)
|
||||||
}
|
}
|
||||||
|
|
||||||
const account = accounts.find(acc => acc.address === selectedAccount?.value)
|
const account = accounts.find(acc => acc.address === selectedAccount?.value)
|
||||||
@@ -125,12 +86,11 @@ const Transaction: FC<TransactionProps> = ({ header, state: txState, ...props })
|
|||||||
throw Error('Account must be selected from imported accounts!')
|
throw Error('Account must be selected from imported accounts!')
|
||||||
}
|
}
|
||||||
const options = prepareOptions(st)
|
const options = prepareOptions(st)
|
||||||
// delete unnecessary fields
|
|
||||||
Object.keys(options).forEach(field => {
|
const fields = getTxFields(options.TransactionType)
|
||||||
if (!options[field]) {
|
if (fields.Destination && !options.Destination) {
|
||||||
delete options[field]
|
throw Error('Destination account is required!')
|
||||||
}
|
}
|
||||||
})
|
|
||||||
|
|
||||||
await sendTransaction(account, options, { logPrefix })
|
await sendTransaction(account, options, { logPrefix })
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
@@ -144,18 +104,23 @@ const Transaction: FC<TransactionProps> = ({ header, state: txState, ...props })
|
|||||||
}
|
}
|
||||||
setState({ txIsLoading: false })
|
setState({ txIsLoading: false })
|
||||||
}, [
|
}, [
|
||||||
txState.selectedTransaction?.value,
|
|
||||||
viewType,
|
viewType,
|
||||||
accounts,
|
accounts,
|
||||||
txIsDisabled,
|
txIsDisabled,
|
||||||
setState,
|
setState,
|
||||||
header,
|
header,
|
||||||
saveEditorState,
|
|
||||||
editorValue,
|
editorValue,
|
||||||
|
txState,
|
||||||
selectedAccount?.value,
|
selectedAccount?.value,
|
||||||
prepareOptions
|
prepareOptions
|
||||||
])
|
])
|
||||||
|
|
||||||
|
const getJsonString = useCallback(
|
||||||
|
(state?: Partial<TransactionState>) =>
|
||||||
|
JSON.stringify(prepareOptions?.(state) || {}, null, editorSettings.tabSize),
|
||||||
|
[editorSettings.tabSize, prepareOptions]
|
||||||
|
)
|
||||||
|
|
||||||
const resetState = useCallback(
|
const resetState = useCallback(
|
||||||
(transactionType: SelectOption | undefined = defaultTransactionType) => {
|
(transactionType: SelectOption | undefined = defaultTransactionType) => {
|
||||||
const fields = getTxFields(transactionType?.value)
|
const fields = getTxFields(transactionType?.value)
|
||||||
@@ -165,12 +130,14 @@ const Transaction: FC<TransactionProps> = ({ header, state: txState, ...props })
|
|||||||
selectedTransaction: transactionType
|
selectedTransaction: transactionType
|
||||||
}
|
}
|
||||||
|
|
||||||
if (transactionType?.value && transactionFlags[transactionType?.value] && fields.Flags) {
|
if (fields.Destination !== undefined) {
|
||||||
nwState.selectedFlags = extractFlags(transactionType.value, fields.Flags)
|
nwState.selectedDestAccount = null
|
||||||
fields.Flags = undefined
|
fields.Destination = ''
|
||||||
|
} else {
|
||||||
|
fields.Destination = undefined
|
||||||
}
|
}
|
||||||
|
|
||||||
nwState.txFields = fields
|
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 })
|
||||||
@@ -201,51 +168,40 @@ const Transaction: FC<TransactionProps> = ({ header, state: txState, ...props })
|
|||||||
[accounts, prepareOptions, setState, txState]
|
[accounts, prepareOptions, setState, txState]
|
||||||
)
|
)
|
||||||
|
|
||||||
const switchToJson = useCallback(() => {
|
|
||||||
const editorValue = getJsonString()
|
|
||||||
setState({ viewType: 'json', editorValue })
|
|
||||||
}, [getJsonString, setState])
|
|
||||||
|
|
||||||
const switchToUI = useCallback(() => {
|
|
||||||
setState({ viewType: 'ui' })
|
|
||||||
}, [setState])
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Box css={{ position: 'relative', height: 'calc(100% - 28px)' }} {...props}>
|
<Box css={{ position: 'relative', height: 'calc(100% - 28px)' }} {...props}>
|
||||||
{viewType === 'json' ? (
|
{viewType === 'json' ? (
|
||||||
<TxJson
|
<TxJson
|
||||||
getJsonString={getJsonString}
|
getJsonString={getJsonString}
|
||||||
saveEditorState={saveEditorState}
|
|
||||||
header={header}
|
header={header}
|
||||||
state={txState}
|
state={txState}
|
||||||
setState={setState}
|
setState={setState}
|
||||||
estimateFee={estimateFee}
|
estimateFee={estimateFee}
|
||||||
/>
|
/>
|
||||||
) : (
|
) : (
|
||||||
<TxUI
|
<TxUI state={txState} setState={setState} estimateFee={estimateFee} />
|
||||||
switchToJson={switchToJson}
|
|
||||||
state={txState}
|
|
||||||
resetState={resetState}
|
|
||||||
setState={setState}
|
|
||||||
estimateFee={estimateFee}
|
|
||||||
/>
|
|
||||||
)}
|
)}
|
||||||
<Flex
|
<Flex
|
||||||
row
|
row
|
||||||
css={{
|
css={{
|
||||||
justifyContent: 'space-between',
|
justifyContent: 'space-between',
|
||||||
position: 'absolute',
|
|
||||||
left: 0,
|
|
||||||
bottom: 0,
|
|
||||||
width: '100%',
|
width: '100%',
|
||||||
mb: '$1'
|
mb: '$2',
|
||||||
|
mt: '$1',
|
||||||
|
'@md': {
|
||||||
|
position: 'absolute',
|
||||||
|
left: 0,
|
||||||
|
bottom: 0,
|
||||||
|
mt: '$0',
|
||||||
|
mb: '$2'
|
||||||
|
}
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Button
|
<Button
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
if (viewType === 'ui') {
|
if (viewType === 'ui') {
|
||||||
switchToJson()
|
setState({ viewType: 'json' })
|
||||||
} else switchToUI()
|
} else setState({ viewType: 'ui' })
|
||||||
}}
|
}}
|
||||||
outline
|
outline
|
||||||
>
|
>
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { FC, useCallback, useEffect, useState } from 'react'
|
import { FC, useCallback, useEffect, useMemo, useState } from 'react'
|
||||||
import { useSnapshot } from 'valtio'
|
import { useSnapshot } from 'valtio'
|
||||||
import state, { transactionsData, TransactionState } from '../../state'
|
import state, { prepareState, transactionsData, TransactionState } from '../../state'
|
||||||
import Text from '../Text'
|
import Text from '../Text'
|
||||||
import { Flex, Link } from '..'
|
import { Flex, Link } from '..'
|
||||||
import { showAlert } from '../../state/actions/showAlert'
|
import { showAlert } from '../../state/actions/showAlert'
|
||||||
@@ -11,27 +11,27 @@ import Monaco from '../Monaco'
|
|||||||
import type monaco from 'monaco-editor'
|
import type monaco from 'monaco-editor'
|
||||||
|
|
||||||
interface JsonProps {
|
interface JsonProps {
|
||||||
getJsonString: (st?: Partial<TransactionState>) => string
|
getJsonString?: (state?: Partial<TransactionState>) => string
|
||||||
saveEditorState: (val?: string, tt?: string) => TransactionState | undefined
|
|
||||||
header?: string
|
header?: string
|
||||||
setState: (pTx?: Partial<TransactionState> | undefined) => void
|
setState: (pTx?: Partial<TransactionState> | undefined) => void
|
||||||
state: TransactionState
|
state: TransactionState
|
||||||
estimateFee?: () => Promise<string | undefined>
|
estimateFee?: () => Promise<string | undefined>
|
||||||
}
|
}
|
||||||
|
|
||||||
export const TxJson: FC<JsonProps> = ({
|
export const TxJson: FC<JsonProps> = ({ getJsonString, state: txState, header, setState }) => {
|
||||||
getJsonString,
|
|
||||||
state: txState,
|
|
||||||
header,
|
|
||||||
setState,
|
|
||||||
saveEditorState
|
|
||||||
}) => {
|
|
||||||
const { editorSettings, accounts } = useSnapshot(state)
|
const { editorSettings, accounts } = useSnapshot(state)
|
||||||
const { editorValue, estimatedFee, editorIsSaved } = txState
|
const { editorValue, estimatedFee } = txState
|
||||||
const [currTxType, setCurrTxType] = useState<string | undefined>(
|
const [currTxType, setCurrTxType] = useState<string | undefined>(
|
||||||
txState.selectedTransaction?.value
|
txState.selectedTransaction?.value
|
||||||
)
|
)
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
setState({
|
||||||
|
editorValue: getJsonString?.()
|
||||||
|
})
|
||||||
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
|
}, [])
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const parsed = parseJSON(editorValue)
|
const parsed = parseJSON(editorValue)
|
||||||
if (!parsed) return
|
if (!parsed) return
|
||||||
@@ -44,19 +44,29 @@ export const TxJson: FC<JsonProps> = ({
|
|||||||
}
|
}
|
||||||
}, [editorValue])
|
}, [editorValue])
|
||||||
|
|
||||||
|
const saveState = (value: string, transactionType?: string) => {
|
||||||
|
const tx = prepareState(value, transactionType)
|
||||||
|
if (tx) {
|
||||||
|
setState(tx)
|
||||||
|
setState({
|
||||||
|
editorValue: getJsonString?.(tx)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const discardChanges = () => {
|
const discardChanges = () => {
|
||||||
showAlert('Confirm', {
|
showAlert('Confirm', {
|
||||||
body: 'Are you sure to discard these changes?',
|
body: 'Are you sure to discard these changes?',
|
||||||
confirmText: 'Yes',
|
confirmText: 'Yes',
|
||||||
onCancel: () => {},
|
onCancel: () => {},
|
||||||
onConfirm: () => setState({ editorValue: getJsonString() })
|
onConfirm: () => setState({ editorValue: getJsonString?.() })
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
const onExit = (value: string) => {
|
const onExit = (value: string) => {
|
||||||
const options = parseJSON(value)
|
const options = parseJSON(value)
|
||||||
if (options) {
|
if (options) {
|
||||||
saveEditorState(value, currTxType)
|
saveState(value, currTxType)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
showAlert('Error!', {
|
showAlert('Error!', {
|
||||||
@@ -153,6 +163,8 @@ export const TxJson: FC<JsonProps> = ({
|
|||||||
})
|
})
|
||||||
}, [getSchemas, monacoInst])
|
}, [getSchemas, monacoInst])
|
||||||
|
|
||||||
|
const hasUnsaved = useMemo(() => editorValue !== getJsonString?.(), [editorValue, getJsonString])
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Monaco
|
<Monaco
|
||||||
rootProps={{
|
rootProps={{
|
||||||
@@ -162,7 +174,7 @@ export const TxJson: FC<JsonProps> = ({
|
|||||||
id={header}
|
id={header}
|
||||||
height="100%"
|
height="100%"
|
||||||
value={editorValue}
|
value={editorValue}
|
||||||
onChange={val => setState({ editorValue: val, editorIsSaved: false })}
|
onChange={val => setState({ editorValue: val })}
|
||||||
onMount={(editor, monaco) => {
|
onMount={(editor, monaco) => {
|
||||||
editor.updateOptions({
|
editor.updateOptions({
|
||||||
minimap: { enabled: false },
|
minimap: { enabled: false },
|
||||||
@@ -178,12 +190,12 @@ export const TxJson: FC<JsonProps> = ({
|
|||||||
model?.onWillDispose(() => onExit(model.getValue()))
|
model?.onWillDispose(() => onExit(model.getValue()))
|
||||||
}}
|
}}
|
||||||
overlay={
|
overlay={
|
||||||
!editorIsSaved ? (
|
hasUnsaved ? (
|
||||||
<Flex row align="center" css={{ fontSize: '$xs', color: '$textMuted', ml: 'auto' }}>
|
<Flex row align="center" css={{ fontSize: '$xs', color: '$textMuted', ml: 'auto' }}>
|
||||||
<Text muted small>
|
<Text muted small>
|
||||||
This file has unsaved changes.
|
This file has unsaved changes.
|
||||||
</Text>
|
</Text>
|
||||||
<Link css={{ ml: '$1' }} onClick={() => saveEditorState(editorValue, currTxType)}>
|
<Link css={{ ml: '$1' }} onClick={() => saveState(editorValue || '', currTxType)}>
|
||||||
save
|
save
|
||||||
</Link>
|
</Link>
|
||||||
<Link css={{ ml: '$1' }} onClick={discardChanges}>
|
<Link css={{ ml: '$1' }} onClick={discardChanges}>
|
||||||
|
|||||||
@@ -1,59 +1,62 @@
|
|||||||
import { FC, ReactNode, useCallback, useEffect, useState } from 'react'
|
import { FC, useCallback, useEffect, useMemo, useState } from 'react'
|
||||||
import Container from '../Container'
|
import Container from '../Container'
|
||||||
import Flex from '../Flex'
|
import Flex from '../Flex'
|
||||||
import Input from '../Input'
|
import Input from '../Input'
|
||||||
import Select, { CreatableSelect } from '../Select'
|
import Select from '../Select'
|
||||||
import Text from '../Text'
|
import Text from '../Text'
|
||||||
import {
|
import {
|
||||||
SelectOption,
|
SelectOption,
|
||||||
TransactionState,
|
TransactionState,
|
||||||
transactionsOptions,
|
transactionsOptions,
|
||||||
TxFields,
|
TxFields,
|
||||||
|
getTxFields,
|
||||||
defaultTransactionType
|
defaultTransactionType
|
||||||
} from '../../state/transactions'
|
} from '../../state/transactions'
|
||||||
import { useSnapshot } from 'valtio'
|
import { useSnapshot } from 'valtio'
|
||||||
import state from '../../state'
|
import state from '../../state'
|
||||||
import { streamState } from '../DebugStream'
|
import { streamState } from '../DebugStream'
|
||||||
import { Box, Button } from '..'
|
import { Button } from '..'
|
||||||
import Textarea from '../Textarea'
|
import Textarea from '../Textarea'
|
||||||
import { getFlags } from '../../state/constants/flags'
|
|
||||||
import { Plus, Trash } from 'phosphor-react'
|
|
||||||
import AccountSequence from '../Sequence'
|
|
||||||
import { capitalize, typeIs } from '../../utils/helpers'
|
|
||||||
|
|
||||||
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>
|
||||||
switchToJson: () => void
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export const TxUI: FC<UIProps> = ({
|
export const TxUI: FC<UIProps> = ({ state: txState, setState, estimateFee }) => {
|
||||||
state: txState,
|
|
||||||
setState,
|
|
||||||
resetState,
|
|
||||||
estimateFee,
|
|
||||||
switchToJson
|
|
||||||
}) => {
|
|
||||||
const { accounts } = useSnapshot(state)
|
const { accounts } = useSnapshot(state)
|
||||||
const { selectedAccount, selectedTransaction, txFields, selectedFlags, hookParameters, memos } =
|
const { selectedAccount, selectedDestAccount, selectedTransaction, txFields } = txState
|
||||||
txState
|
|
||||||
|
|
||||||
const accountOptions: SelectOption[] = accounts.map(acc => ({
|
const accountOptions: SelectOption[] = accounts.map(acc => ({
|
||||||
label: acc.name,
|
label: acc.name,
|
||||||
value: acc.address
|
value: acc.address
|
||||||
}))
|
}))
|
||||||
|
|
||||||
const flagsOptions: SelectOption[] = Object.entries(
|
const destAccountOptions: SelectOption[] = accounts
|
||||||
getFlags(selectedTransaction?.value) || {}
|
.map(acc => ({
|
||||||
).map(([label, value]) => ({
|
label: acc.name,
|
||||||
label,
|
value: acc.address
|
||||||
value
|
}))
|
||||||
}))
|
.filter(acc => acc.value !== selectedAccount?.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
|
||||||
@@ -73,22 +76,6 @@ export const TxUI: FC<UIProps> = ({
|
|||||||
[setState, txFields]
|
[setState, txFields]
|
||||||
)
|
)
|
||||||
|
|
||||||
const setRawField = useCallback(
|
|
||||||
(field: keyof TxFields, type: string, value: any) => {
|
|
||||||
// TODO $type should be a narrowed type
|
|
||||||
setState({
|
|
||||||
txFields: {
|
|
||||||
...txFields,
|
|
||||||
[field]: {
|
|
||||||
$type: type,
|
|
||||||
$value: value
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
},
|
|
||||||
[setState, txFields]
|
|
||||||
)
|
|
||||||
|
|
||||||
const handleEstimateFee = useCallback(
|
const handleEstimateFee = useCallback(
|
||||||
async (state?: TransactionState, silent?: boolean) => {
|
async (state?: TransactionState, silent?: boolean) => {
|
||||||
setFeeLoading(true)
|
setFeeLoading(true)
|
||||||
@@ -105,13 +92,15 @@ export const TxUI: FC<UIProps> = ({
|
|||||||
(tt: SelectOption) => {
|
(tt: SelectOption) => {
|
||||||
setState({ selectedTransaction: tt })
|
setState({ selectedTransaction: tt })
|
||||||
|
|
||||||
const newState = resetState(tt)
|
const newState = resetFields(tt.value)
|
||||||
|
|
||||||
handleEstimateFee(newState, true)
|
handleEstimateFee(newState, true)
|
||||||
},
|
},
|
||||||
[handleEstimateFee, resetState, setState]
|
[handleEstimateFee, resetFields, setState]
|
||||||
)
|
)
|
||||||
|
|
||||||
|
const switchToJson = () => setState({ viewType: 'json' })
|
||||||
|
|
||||||
// default tx
|
// default tx
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (selectedTransaction?.value) return
|
if (selectedTransaction?.value) return
|
||||||
@@ -121,23 +110,20 @@ export const TxUI: FC<UIProps> = ({
|
|||||||
}
|
}
|
||||||
}, [handleChangeTxType, selectedTransaction?.value])
|
}, [handleChangeTxType, selectedTransaction?.value])
|
||||||
|
|
||||||
const richFields = ['TransactionType', 'Account', 'HookParameters', 'Memos']
|
const fields = useMemo(
|
||||||
|
() => getTxFields(selectedTransaction?.value),
|
||||||
|
[selectedTransaction?.value]
|
||||||
|
)
|
||||||
|
|
||||||
if (flagsOptions.length) {
|
const specialFields = ['TransactionType', 'Account']
|
||||||
richFields.push('Flags')
|
if (fields.Destination !== undefined) {
|
||||||
|
specialFields.push('Destination')
|
||||||
}
|
}
|
||||||
|
|
||||||
const otherFields = Object.keys(txFields).filter(k => !richFields.includes(k)) as [keyof TxFields]
|
const otherFields = Object.keys(txFields).filter(k => !specialFields.includes(k)) as [
|
||||||
const amountOptions = [
|
keyof TxFields
|
||||||
{ label: 'XRP', value: 'xrp' },
|
]
|
||||||
{ label: 'Token', value: 'token' }
|
|
||||||
] as const
|
|
||||||
|
|
||||||
const defaultTokenAmount = {
|
|
||||||
value: '0',
|
|
||||||
currency: '',
|
|
||||||
issuer: ''
|
|
||||||
}
|
|
||||||
return (
|
return (
|
||||||
<Container
|
<Container
|
||||||
css={{
|
css={{
|
||||||
@@ -147,437 +133,222 @@ export const TxUI: FC<UIProps> = ({
|
|||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Flex column fluid css={{ height: '100%', overflowY: 'auto', pr: '$1' }}>
|
<Flex column fluid css={{ height: '100%', overflowY: 'auto', pr: '$1' }}>
|
||||||
<TxField label="Transaction type">
|
<Flex
|
||||||
|
row
|
||||||
|
fluid
|
||||||
|
css={{
|
||||||
|
justifyContent: 'space-between',
|
||||||
|
alignItems: 'center',
|
||||||
|
mb: '$3',
|
||||||
|
mt: '1px',
|
||||||
|
pr: '1px',
|
||||||
|
'@xl': {
|
||||||
|
justifyContent: 'flex-end'
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Text muted css={{ mr: '$3' }}>
|
||||||
|
Transaction type:{' '}
|
||||||
|
</Text>
|
||||||
<Select
|
<Select
|
||||||
instanceId="transactionsType"
|
instanceId="transactionsType"
|
||||||
placeholder="Select transaction type"
|
placeholder="Select transaction type"
|
||||||
options={transactionsOptions}
|
options={transactionsOptions}
|
||||||
hideSelectedOptions
|
hideSelectedOptions
|
||||||
|
css={{
|
||||||
|
width: '60%',
|
||||||
|
minWidth: '200px',
|
||||||
|
'@lg': {
|
||||||
|
width: '70%'
|
||||||
|
}
|
||||||
|
}}
|
||||||
value={selectedTransaction}
|
value={selectedTransaction}
|
||||||
onChange={(tt: any) => handleChangeTxType(tt)}
|
onChange={(tt: any) => handleChangeTxType(tt)}
|
||||||
/>
|
/>
|
||||||
</TxField>
|
</Flex>
|
||||||
<TxField label="Account">
|
<Flex
|
||||||
|
row
|
||||||
|
fluid
|
||||||
|
css={{
|
||||||
|
justifyContent: 'space-between',
|
||||||
|
alignItems: 'center',
|
||||||
|
mb: '$3',
|
||||||
|
pr: '1px',
|
||||||
|
'@xl': {
|
||||||
|
justifyContent: 'flex-end'
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Text muted css={{ mr: '$3' }}>
|
||||||
|
Account:{' '}
|
||||||
|
</Text>
|
||||||
<Select
|
<Select
|
||||||
instanceId="from-account"
|
instanceId="from-account"
|
||||||
placeholder="Select your account"
|
placeholder="Select your account"
|
||||||
|
css={{
|
||||||
|
width: '60%',
|
||||||
|
minWidth: '200px',
|
||||||
|
'@lg': {
|
||||||
|
width: '70%'
|
||||||
|
}
|
||||||
|
}}
|
||||||
options={accountOptions}
|
options={accountOptions}
|
||||||
value={selectedAccount}
|
value={selectedAccount}
|
||||||
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
|
||||||
/>
|
/>
|
||||||
</TxField>
|
</Flex>
|
||||||
<TxField label="Sequence">
|
{fields.Destination !== undefined && (
|
||||||
<AccountSequence address={selectedAccount?.value} />
|
<Flex
|
||||||
</TxField>
|
row
|
||||||
{richFields.includes('Flags') && (
|
fluid
|
||||||
<TxField label="Flags">
|
css={{
|
||||||
<Select
|
justifyContent: 'space-between',
|
||||||
isClearable
|
alignItems: 'center',
|
||||||
instanceId="flags"
|
mb: '$3',
|
||||||
placeholder="Select flags to apply"
|
pr: '1px',
|
||||||
menuPosition="fixed"
|
'@xl': {
|
||||||
value={selectedFlags}
|
justifyContent: 'flex-end'
|
||||||
isMulti
|
|
||||||
options={flagsOptions}
|
|
||||||
onChange={flags => setState({ selectedFlags: flags as any })}
|
|
||||||
closeMenuOnSelect={
|
|
||||||
selectedFlags ? selectedFlags.length >= flagsOptions.length - 1 : false
|
|
||||||
}
|
}
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Text muted css={{ mr: '$3' }}>
|
||||||
|
Destination account:{' '}
|
||||||
|
</Text>
|
||||||
|
<Select
|
||||||
|
instanceId="to-account"
|
||||||
|
placeholder="Select the destination account"
|
||||||
|
css={{
|
||||||
|
width: '60%',
|
||||||
|
minWidth: '200px',
|
||||||
|
'@lg': {
|
||||||
|
width: '70%'
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
options={destAccountOptions}
|
||||||
|
value={selectedDestAccount}
|
||||||
|
isClearable
|
||||||
|
onChange={(acc: any) => setState({ selectedDestAccount: acc })}
|
||||||
/>
|
/>
|
||||||
</TxField>
|
</Flex>
|
||||||
)}
|
)}
|
||||||
{otherFields.map(field => {
|
{otherFields.map(field => {
|
||||||
let _value = txFields[field]
|
let _value = txFields[field]
|
||||||
|
|
||||||
let value: string | undefined
|
let value: string | undefined
|
||||||
if (typeIs(_value, 'object')) {
|
if (typeof _value === 'object') {
|
||||||
if (_value.$type === 'json' && typeIs(_value.$value, ['object', 'array'])) {
|
if (_value.$type === 'json' && typeof _value.$value === 'object') {
|
||||||
value = JSON.stringify(_value.$value, null, 2)
|
value = JSON.stringify(_value.$value, null, 2)
|
||||||
} else {
|
} else {
|
||||||
value = _value.$value?.toString()
|
value = _value.$value.toString()
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
value = _value?.toString()
|
value = _value?.toString()
|
||||||
}
|
}
|
||||||
|
|
||||||
const isAccount = typeIs(_value, 'object') && _value.$type === 'account'
|
const isXrp = typeof _value === 'object' && _value.$type === 'xrp'
|
||||||
const isXrpAmount = typeIs(_value, 'object') && _value.$type === 'amount.xrp'
|
|
||||||
const isTokenAmount = typeIs(_value, 'object') && _value.$type === 'amount.token'
|
|
||||||
const isJson = typeof _value === 'object' && _value.$type === 'json'
|
const isJson = typeof _value === 'object' && _value.$type === 'json'
|
||||||
const isFee = field === 'Fee'
|
const isFee = field === 'Fee'
|
||||||
let rows = isJson ? (value?.match(/\n/gm)?.length || 0) + 1 : undefined
|
let rows = isJson ? (value?.match(/\n/gm)?.length || 0) + 1 : undefined
|
||||||
if (rows && rows > 5) rows = 5
|
if (rows && rows > 5) rows = 5
|
||||||
let tokenAmount = defaultTokenAmount
|
|
||||||
if (isTokenAmount && typeIs(_value, 'object') && typeIs(_value.$value, 'object')) {
|
|
||||||
tokenAmount = {
|
|
||||||
value: _value.$value.value,
|
|
||||||
currency: _value.$value.currency,
|
|
||||||
issuer: _value.$value.issuer
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (isXrpAmount || isTokenAmount) {
|
|
||||||
return (
|
|
||||||
<TxField key={field} label={field}>
|
|
||||||
<Flex fluid css={{ alignItems: 'center' }}>
|
|
||||||
{isTokenAmount ? (
|
|
||||||
<Flex
|
|
||||||
fluid
|
|
||||||
row
|
|
||||||
align="center"
|
|
||||||
justify="space-between"
|
|
||||||
css={{ position: 'relative' }}
|
|
||||||
>
|
|
||||||
{/* <Input
|
|
||||||
type="text"
|
|
||||||
placeholder="Issuer"
|
|
||||||
value={tokenAmount.issuer}
|
|
||||||
onChange={e =>
|
|
||||||
setRawField(field, 'amount.token', {
|
|
||||||
...tokenAmount,
|
|
||||||
issuer: e.target.value
|
|
||||||
})
|
|
||||||
}
|
|
||||||
/> */}
|
|
||||||
<Input
|
|
||||||
type="text"
|
|
||||||
value={tokenAmount.currency}
|
|
||||||
placeholder="Currency"
|
|
||||||
onChange={e => {
|
|
||||||
setRawField(field, 'amount.token', {
|
|
||||||
...tokenAmount,
|
|
||||||
currency: e.target.value
|
|
||||||
})
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
<Input
|
|
||||||
css={{ mx: '$1' }}
|
|
||||||
type="number"
|
|
||||||
value={tokenAmount.value}
|
|
||||||
placeholder="Value"
|
|
||||||
onChange={e => {
|
|
||||||
setRawField(field, 'amount.token', {
|
|
||||||
...tokenAmount,
|
|
||||||
value: e.target.value
|
|
||||||
})
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
<Box css={{ width: '50%' }}>
|
|
||||||
<CreatableAccount
|
|
||||||
value={tokenAmount.issuer}
|
|
||||||
field={'Issuer' as any}
|
|
||||||
placeholder="Issuer"
|
|
||||||
setField={(_, value = '') => {
|
|
||||||
setRawField(field, 'amount.token', {
|
|
||||||
...tokenAmount,
|
|
||||||
issuer: value
|
|
||||||
})
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
</Box>
|
|
||||||
</Flex>
|
|
||||||
) : (
|
|
||||||
<Input
|
|
||||||
css={{ flex: 'inherit' }}
|
|
||||||
type="number"
|
|
||||||
value={value}
|
|
||||||
onChange={e => handleSetField(field, e.target.value)}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
<Box
|
|
||||||
css={{
|
|
||||||
ml: '$2',
|
|
||||||
width: '150px'
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<Select
|
|
||||||
instanceId="currency-type"
|
|
||||||
options={amountOptions}
|
|
||||||
value={isXrpAmount ? amountOptions['0'] : amountOptions['1']}
|
|
||||||
onChange={(e: any) => {
|
|
||||||
const opt = e as typeof amountOptions[number]
|
|
||||||
if (opt.value === 'xrp') {
|
|
||||||
setRawField(field, 'amount.xrp', '0')
|
|
||||||
} else {
|
|
||||||
setRawField(field, 'amount.token', defaultTokenAmount)
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
</Box>
|
|
||||||
</Flex>
|
|
||||||
</TxField>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
if (isAccount) {
|
|
||||||
return (
|
|
||||||
<TxField key={field} label={field}>
|
|
||||||
<CreatableAccount value={value} field={field} setField={handleSetField} />
|
|
||||||
</TxField>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
return (
|
return (
|
||||||
<TxField key={field} label={field}>
|
<Flex column key={field} css={{ mb: '$2', pr: '1px' }}>
|
||||||
{isJson ? (
|
<Flex
|
||||||
<Textarea
|
row
|
||||||
rows={rows}
|
fluid
|
||||||
value={value}
|
css={{
|
||||||
spellCheck={false}
|
justifyContent: 'space-between',
|
||||||
onChange={switchToJson}
|
alignItems: 'center',
|
||||||
css={{
|
position: 'relative',
|
||||||
flex: 'inherit',
|
'@xl': {
|
||||||
resize: 'vertical'
|
justifyContent: 'flex-end'
|
||||||
}}
|
|
||||||
/>
|
|
||||||
) : (
|
|
||||||
<Input
|
|
||||||
type={isFee ? 'number' : 'text'}
|
|
||||||
value={value}
|
|
||||||
onChange={e => {
|
|
||||||
if (isFee) {
|
|
||||||
const val = e.target.value.replaceAll('.', '').replaceAll(',', '')
|
|
||||||
handleSetField(field, val)
|
|
||||||
} else {
|
|
||||||
handleSetField(field, e.target.value)
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
onKeyPress={
|
|
||||||
isFee
|
|
||||||
? e => {
|
|
||||||
if (e.key === '.' || e.key === ',') {
|
|
||||||
e.preventDefault()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
: undefined
|
|
||||||
}
|
}
|
||||||
css={{
|
}}
|
||||||
flex: 'inherit',
|
>
|
||||||
'-moz-appearance': 'textfield',
|
<Text muted css={{ mr: '$3' }}>
|
||||||
'&::-webkit-outer-spin-button': {
|
{field + (isXrp ? ' (XRP)' : '')}:{' '}
|
||||||
'-webkit-appearance': 'none',
|
</Text>
|
||||||
margin: 0
|
{isJson ? (
|
||||||
},
|
<Textarea
|
||||||
'&::-webkit-inner-spin-button ': {
|
rows={rows}
|
||||||
'-webkit-appearance': 'none',
|
value={value}
|
||||||
margin: 0
|
spellCheck={false}
|
||||||
}
|
onChange={switchToJson}
|
||||||
}}
|
css={{
|
||||||
/>
|
width: '60%',
|
||||||
)}
|
minWidth: '200px',
|
||||||
{isFee && (
|
flex: 'inherit',
|
||||||
<Button
|
resize: 'vertical',
|
||||||
size="xs"
|
'@lg': {
|
||||||
variant="primary"
|
width: '70%'
|
||||||
outline
|
}
|
||||||
disabled={txState.txIsDisabled}
|
|
||||||
isDisabled={txState.txIsDisabled}
|
|
||||||
isLoading={feeLoading}
|
|
||||||
css={{
|
|
||||||
position: 'absolute',
|
|
||||||
right: '$2',
|
|
||||||
fontSize: '$xs',
|
|
||||||
cursor: 'pointer',
|
|
||||||
alignContent: 'center',
|
|
||||||
display: 'flex'
|
|
||||||
}}
|
|
||||||
onClick={() => handleEstimateFee()}
|
|
||||||
>
|
|
||||||
Suggest
|
|
||||||
</Button>
|
|
||||||
)}
|
|
||||||
</TxField>
|
|
||||||
)
|
|
||||||
})}
|
|
||||||
<TxField multiLine label="Hook parameters">
|
|
||||||
<Flex column fluid>
|
|
||||||
{Object.entries(hookParameters).map(([id, { label, value }]) => (
|
|
||||||
<Flex column key={id} css={{ mb: '$2' }}>
|
|
||||||
<Flex row>
|
|
||||||
<Input
|
|
||||||
placeholder="Parameter name"
|
|
||||||
value={label}
|
|
||||||
onChange={e => {
|
|
||||||
setState({
|
|
||||||
hookParameters: {
|
|
||||||
...hookParameters,
|
|
||||||
[id]: { label: e.target.value, value }
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
|
) : (
|
||||||
<Input
|
<Input
|
||||||
css={{ mx: '$2' }}
|
type={isFee ? 'number' : 'text'}
|
||||||
placeholder="Value"
|
|
||||||
value={value}
|
value={value}
|
||||||
onChange={e => {
|
onChange={e => {
|
||||||
setState({
|
if (isFee) {
|
||||||
hookParameters: {
|
const val = e.target.value.replaceAll('.', '').replaceAll(',', '')
|
||||||
...hookParameters,
|
handleSetField(field, val)
|
||||||
[id]: { label, value: e.target.value }
|
} else {
|
||||||
}
|
handleSetField(field, e.target.value)
|
||||||
})
|
}
|
||||||
|
}}
|
||||||
|
onKeyPress={
|
||||||
|
isFee
|
||||||
|
? e => {
|
||||||
|
if (e.key === '.' || e.key === ',') {
|
||||||
|
e.preventDefault()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
: undefined
|
||||||
|
}
|
||||||
|
css={{
|
||||||
|
width: '60%',
|
||||||
|
minWidth: '200px',
|
||||||
|
flex: 'inherit',
|
||||||
|
'-moz-appearance': 'textfield',
|
||||||
|
'&::-webkit-outer-spin-button': {
|
||||||
|
'-webkit-appearance': 'none',
|
||||||
|
margin: 0
|
||||||
|
},
|
||||||
|
'&::-webkit-inner-spin-button ': {
|
||||||
|
'-webkit-appearance': 'none',
|
||||||
|
margin: 0
|
||||||
|
},
|
||||||
|
'@lg': {
|
||||||
|
width: '70%'
|
||||||
|
}
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
|
)}
|
||||||
|
{isFee && (
|
||||||
<Button
|
<Button
|
||||||
onClick={() => {
|
size="xs"
|
||||||
const { [id]: _, ...rest } = hookParameters
|
variant="primary"
|
||||||
setState({ hookParameters: rest })
|
outline
|
||||||
|
disabled={txState.txIsDisabled}
|
||||||
|
isDisabled={txState.txIsDisabled}
|
||||||
|
isLoading={feeLoading}
|
||||||
|
css={{
|
||||||
|
position: 'absolute',
|
||||||
|
right: '$2',
|
||||||
|
fontSize: '$xs',
|
||||||
|
cursor: 'pointer',
|
||||||
|
alignContent: 'center',
|
||||||
|
display: 'flex'
|
||||||
}}
|
}}
|
||||||
variant="destroy"
|
onClick={() => handleEstimateFee()}
|
||||||
>
|
>
|
||||||
<Trash weight="regular" size="16px" />
|
Suggest
|
||||||
</Button>
|
</Button>
|
||||||
</Flex>
|
)}
|
||||||
</Flex>
|
</Flex>
|
||||||
))}
|
</Flex>
|
||||||
<Button
|
)
|
||||||
outline
|
})}
|
||||||
fullWidth
|
|
||||||
type="button"
|
|
||||||
onClick={() => {
|
|
||||||
const id = Object.keys(hookParameters).length
|
|
||||||
setState({
|
|
||||||
hookParameters: { ...hookParameters, [id]: { label: '', value: '' } }
|
|
||||||
})
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<Plus size="16px" />
|
|
||||||
Add Hook Parameter
|
|
||||||
</Button>
|
|
||||||
</Flex>
|
|
||||||
</TxField>
|
|
||||||
<TxField multiLine label="Memos">
|
|
||||||
<Flex column fluid>
|
|
||||||
{Object.entries(memos).map(([id, memo]) => (
|
|
||||||
<Flex column key={id} css={{ mb: '$2' }}>
|
|
||||||
<Flex
|
|
||||||
row
|
|
||||||
css={{
|
|
||||||
flexWrap: 'wrap',
|
|
||||||
width: '100%'
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<Input
|
|
||||||
placeholder="Memo type"
|
|
||||||
value={memo.type}
|
|
||||||
onChange={e => {
|
|
||||||
setState({
|
|
||||||
memos: {
|
|
||||||
...memos,
|
|
||||||
[id]: { ...memo, type: e.target.value }
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
<Input
|
|
||||||
placeholder="Data"
|
|
||||||
css={{ mx: '$2' }}
|
|
||||||
value={memo.data}
|
|
||||||
onChange={e => {
|
|
||||||
setState({
|
|
||||||
memos: {
|
|
||||||
...memos,
|
|
||||||
[id]: { ...memo, data: e.target.value }
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
<Input
|
|
||||||
placeholder="Format"
|
|
||||||
value={memo.format}
|
|
||||||
onChange={e => {
|
|
||||||
setState({
|
|
||||||
memos: {
|
|
||||||
...memos,
|
|
||||||
[id]: { ...memo, format: e.target.value }
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
<Button
|
|
||||||
css={{ ml: '$2' }}
|
|
||||||
onClick={() => {
|
|
||||||
const { [id]: _, ...rest } = memos
|
|
||||||
setState({ memos: rest })
|
|
||||||
}}
|
|
||||||
variant="destroy"
|
|
||||||
>
|
|
||||||
<Trash weight="regular" size="16px" />
|
|
||||||
</Button>
|
|
||||||
</Flex>
|
|
||||||
</Flex>
|
|
||||||
))}
|
|
||||||
<Button
|
|
||||||
outline
|
|
||||||
fullWidth
|
|
||||||
type="button"
|
|
||||||
onClick={() => {
|
|
||||||
const id = Object.keys(memos).length
|
|
||||||
setState({
|
|
||||||
memos: { ...memos, [id]: { data: '', format: '', type: '' } }
|
|
||||||
})
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<Plus size="16px" />
|
|
||||||
Add Memo
|
|
||||||
</Button>
|
|
||||||
</Flex>
|
|
||||||
</TxField>
|
|
||||||
</Flex>
|
</Flex>
|
||||||
</Container>
|
</Container>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
export const CreatableAccount: FC<{
|
|
||||||
value: string | undefined
|
|
||||||
field: keyof TxFields
|
|
||||||
placeholder?: string
|
|
||||||
setField: (field: keyof TxFields, value: string, opFields?: TxFields) => void
|
|
||||||
}> = ({ value, field, setField, placeholder }) => {
|
|
||||||
const { accounts } = useSnapshot(state)
|
|
||||||
const accountOptions: SelectOption[] = accounts.map(acc => ({
|
|
||||||
label: acc.name,
|
|
||||||
value: acc.address
|
|
||||||
}))
|
|
||||||
const label = accountOptions.find(a => a.value === value)?.label || value
|
|
||||||
const val = {
|
|
||||||
value,
|
|
||||||
label
|
|
||||||
}
|
|
||||||
placeholder = placeholder || `${capitalize(field)} account`
|
|
||||||
return (
|
|
||||||
<CreatableSelect
|
|
||||||
isClearable
|
|
||||||
instanceId={field}
|
|
||||||
placeholder={placeholder}
|
|
||||||
options={accountOptions}
|
|
||||||
value={value ? val : undefined}
|
|
||||||
onChange={(acc: any) => setField(field, acc?.value)}
|
|
||||||
/>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
export const TxField: FC<{ label: string; children: ReactNode; multiLine?: boolean }> = ({
|
|
||||||
label,
|
|
||||||
children,
|
|
||||||
multiLine = false
|
|
||||||
}) => {
|
|
||||||
return (
|
|
||||||
<Flex
|
|
||||||
row
|
|
||||||
fluid
|
|
||||||
css={{
|
|
||||||
justifyContent: 'flex-end',
|
|
||||||
alignItems: multiLine ? 'flex-start' : 'center',
|
|
||||||
position: 'relative',
|
|
||||||
mb: '$2',
|
|
||||||
mt: '1px',
|
|
||||||
pr: '1px'
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<Text muted css={{ mr: '$3', mt: multiLine ? '$2' : 0 }}>
|
|
||||||
{label}:{' '}
|
|
||||||
</Text>
|
|
||||||
<Flex css={{ width: '70%', alignItems: 'center' }}>{children}</Flex>
|
|
||||||
</Flex>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -2,14 +2,11 @@
|
|||||||
{
|
{
|
||||||
"TransactionType": "AccountDelete",
|
"TransactionType": "AccountDelete",
|
||||||
"Account": "rWYkbWkCeg8dP6rXALnjgZSjjLyih5NXm",
|
"Account": "rWYkbWkCeg8dP6rXALnjgZSjjLyih5NXm",
|
||||||
"Destination": {
|
"Destination": "rPT1Sjq2YGrBMTttX4GZHjKu9dyfzbpAYe",
|
||||||
"$type": "account",
|
|
||||||
"$value": ""
|
|
||||||
},
|
|
||||||
"DestinationTag": 13,
|
"DestinationTag": 13,
|
||||||
"Fee": "2000000",
|
"Fee": "2000000",
|
||||||
"Sequence": 2470665,
|
"Sequence": 2470665,
|
||||||
"Flags": "2147483648"
|
"Flags": 2147483648
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"TransactionType": "AccountSet",
|
"TransactionType": "AccountSet",
|
||||||
@@ -31,11 +28,7 @@
|
|||||||
"TransactionType": "CheckCash",
|
"TransactionType": "CheckCash",
|
||||||
"Amount": {
|
"Amount": {
|
||||||
"$value": "100",
|
"$value": "100",
|
||||||
"$type": "amount.xrp"
|
"$type": "xrp"
|
||||||
},
|
|
||||||
"DeliverMin": {
|
|
||||||
"$value": "",
|
|
||||||
"$type": "amount.xrp"
|
|
||||||
},
|
},
|
||||||
"CheckID": "838766BA2B995C00744175F69A1B11E32C3DBC40E64801A4056FCBD657F57334",
|
"CheckID": "838766BA2B995C00744175F69A1B11E32C3DBC40E64801A4056FCBD657F57334",
|
||||||
"Fee": "12"
|
"Fee": "12"
|
||||||
@@ -43,10 +36,7 @@
|
|||||||
{
|
{
|
||||||
"TransactionType": "CheckCreate",
|
"TransactionType": "CheckCreate",
|
||||||
"Account": "rUn84CUYbNjRoTQ6mSW7BVJPSVJNLb1QLo",
|
"Account": "rUn84CUYbNjRoTQ6mSW7BVJPSVJNLb1QLo",
|
||||||
"Destination": {
|
"Destination": "rfkE1aSy9G8Upk4JssnwBxhEv5p4mn2KTy",
|
||||||
"$type": "account",
|
|
||||||
"$value": ""
|
|
||||||
},
|
|
||||||
"SendMax": "100000000",
|
"SendMax": "100000000",
|
||||||
"Expiration": 570113521,
|
"Expiration": 570113521,
|
||||||
"InvoiceID": "6F1DFD1D0FE8A32E40E1F2C05CF1C15545BAB56B617F9C6C2D63A6B704BEF59B",
|
"InvoiceID": "6F1DFD1D0FE8A32E40E1F2C05CF1C15545BAB56B617F9C6C2D63A6B704BEF59B",
|
||||||
@@ -58,16 +48,13 @@
|
|||||||
"Account": "rsUiUMpnrgxQp24dJYZDhmV4bE3aBtQyt8",
|
"Account": "rsUiUMpnrgxQp24dJYZDhmV4bE3aBtQyt8",
|
||||||
"Authorize": "rEhxGqkqPPSxQ3P25J66ft5TwpzV14k2de",
|
"Authorize": "rEhxGqkqPPSxQ3P25J66ft5TwpzV14k2de",
|
||||||
"Fee": "10",
|
"Fee": "10",
|
||||||
"Flags": "2147483648",
|
"Flags": 2147483648,
|
||||||
"Sequence": 2
|
"Sequence": 2
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"Account": "rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn",
|
"Account": "rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn",
|
||||||
"TransactionType": "EscrowCancel",
|
"TransactionType": "EscrowCancel",
|
||||||
"Owner": {
|
"Owner": "rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn",
|
||||||
"$type": "account",
|
|
||||||
"$value": ""
|
|
||||||
},
|
|
||||||
"OfferSequence": 7,
|
"OfferSequence": 7,
|
||||||
"Fee": "10"
|
"Fee": "10"
|
||||||
},
|
},
|
||||||
@@ -76,12 +63,9 @@
|
|||||||
"TransactionType": "EscrowCreate",
|
"TransactionType": "EscrowCreate",
|
||||||
"Amount": {
|
"Amount": {
|
||||||
"$value": "100",
|
"$value": "100",
|
||||||
"$type": "amount.xrp"
|
"$type": "xrp"
|
||||||
},
|
|
||||||
"Destination": {
|
|
||||||
"$type": "account",
|
|
||||||
"$value": ""
|
|
||||||
},
|
},
|
||||||
|
"Destination": "rsA2LpzuawewSBQXkiju3YQTMzW13pAAdW",
|
||||||
"CancelAfter": 533257958,
|
"CancelAfter": 533257958,
|
||||||
"FinishAfter": 533171558,
|
"FinishAfter": 533171558,
|
||||||
"Condition": "A0258020E3B0C44298FC1C149AFBF4C8996FB92427AE41E4649B934CA495991B7852B855810100",
|
"Condition": "A0258020E3B0C44298FC1C149AFBF4C8996FB92427AE41E4649B934CA495991B7852B855810100",
|
||||||
@@ -92,10 +76,7 @@
|
|||||||
{
|
{
|
||||||
"Account": "rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn",
|
"Account": "rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn",
|
||||||
"TransactionType": "EscrowFinish",
|
"TransactionType": "EscrowFinish",
|
||||||
"Owner": {
|
"Owner": "rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn",
|
||||||
"$type": "account",
|
|
||||||
"$value": ""
|
|
||||||
},
|
|
||||||
"OfferSequence": 7,
|
"OfferSequence": 7,
|
||||||
"Condition": "A0258020E3B0C44298FC1C149AFBF4C8996FB92427AE41E4649B934CA495991B7852B855810100",
|
"Condition": "A0258020E3B0C44298FC1C149AFBF4C8996FB92427AE41E4649B934CA495991B7852B855810100",
|
||||||
"Fulfillment": "A0028000",
|
"Fulfillment": "A0028000",
|
||||||
@@ -128,9 +109,7 @@
|
|||||||
"Fee": "10",
|
"Fee": "10",
|
||||||
"NFTokenOffers": {
|
"NFTokenOffers": {
|
||||||
"$type": "json",
|
"$type": "json",
|
||||||
"$value": [
|
"$value": ["4AAAEEA76E3C8148473CB3840CE637676E561FB02BD4CA22CA59729EA815B862"]
|
||||||
"4AAAEEA76E3C8148473CB3840CE637676E561FB02BD4CA22CA59729EA815B862"
|
|
||||||
]
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -139,24 +118,17 @@
|
|||||||
"NFTokenID": "000100001E962F495F07A990F4ED55ACCFEEF365DBAA76B6A048C0A200000007",
|
"NFTokenID": "000100001E962F495F07A990F4ED55ACCFEEF365DBAA76B6A048C0A200000007",
|
||||||
"Amount": {
|
"Amount": {
|
||||||
"$value": "100",
|
"$value": "100",
|
||||||
"$type": "amount.xrp"
|
"$type": "xrp"
|
||||||
},
|
|
||||||
"Flags": "1",
|
|
||||||
"Destination": {
|
|
||||||
"$type": "account",
|
|
||||||
"$value": ""
|
|
||||||
},
|
|
||||||
"Owner": {
|
|
||||||
"$type": "account",
|
|
||||||
"$value": ""
|
|
||||||
},
|
},
|
||||||
|
"Flags": 1,
|
||||||
|
"Destination": "",
|
||||||
"Fee": "10"
|
"Fee": "10"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"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
|
||||||
@@ -165,35 +137,25 @@
|
|||||||
"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": {
|
"TakerGets": "6000000",
|
||||||
"$type": "amount.xrp",
|
"Amount": {
|
||||||
"$value": "6000000"
|
"$value": "100",
|
||||||
},
|
"$type": "xrp"
|
||||||
"TakerPays": {
|
|
||||||
"$type": "amount.token",
|
|
||||||
"$value": {
|
|
||||||
"currency": "GKO",
|
|
||||||
"issuer": "ruazs5h1qEsqpke88pcqnaseXdm6od2xc",
|
|
||||||
"value": "2"
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"TransactionType": "Payment",
|
"TransactionType": "Payment",
|
||||||
"Account": "rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn",
|
"Account": "rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn",
|
||||||
"Destination": {
|
"Destination": "ra5nK24KXen9AHvsdFTKHSANinZseWnPcX",
|
||||||
"$type": "account",
|
|
||||||
"$value": ""
|
|
||||||
},
|
|
||||||
"Amount": {
|
"Amount": {
|
||||||
"$value": "100",
|
"$value": "100",
|
||||||
"$type": "amount.xrp"
|
"$type": "xrp"
|
||||||
},
|
},
|
||||||
"Fee": "12",
|
"Fee": "12",
|
||||||
"Flags": "2147483648",
|
"Flags": 2147483648,
|
||||||
"Sequence": 2
|
"Sequence": 2
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -201,12 +163,9 @@
|
|||||||
"TransactionType": "PaymentChannelCreate",
|
"TransactionType": "PaymentChannelCreate",
|
||||||
"Amount": {
|
"Amount": {
|
||||||
"$value": "100",
|
"$value": "100",
|
||||||
"$type": "amount.xrp"
|
"$type": "xrp"
|
||||||
},
|
|
||||||
"Destination": {
|
|
||||||
"$type": "account",
|
|
||||||
"$value": ""
|
|
||||||
},
|
},
|
||||||
|
"Destination": "rsA2LpzuawewSBQXkiju3YQTMzW13pAAdW",
|
||||||
"SettleDelay": 86400,
|
"SettleDelay": 86400,
|
||||||
"PublicKey": "32D2471DB72B27E3310F355BB33E339BF26F8392D5A93D3BC0FC3B566612DA0F0A",
|
"PublicKey": "32D2471DB72B27E3310F355BB33E339BF26F8392D5A93D3BC0FC3B566612DA0F0A",
|
||||||
"CancelAfter": 533171558,
|
"CancelAfter": 533171558,
|
||||||
@@ -220,20 +179,20 @@
|
|||||||
"Channel": "C1AE6DDDEEC05CF2978C0BAD6FE302948E9533691DC749DCDD3B9E5992CA6198",
|
"Channel": "C1AE6DDDEEC05CF2978C0BAD6FE302948E9533691DC749DCDD3B9E5992CA6198",
|
||||||
"Amount": {
|
"Amount": {
|
||||||
"$value": "200",
|
"$value": "200",
|
||||||
"$type": "amount.xrp"
|
"$type": "xrp"
|
||||||
},
|
},
|
||||||
"Expiration": 543171558,
|
"Expiration": 543171558,
|
||||||
"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",
|
||||||
@@ -273,10 +232,10 @@
|
|||||||
"TransactionType": "TrustSet",
|
"TransactionType": "TrustSet",
|
||||||
"Account": "ra5nK24KXen9AHvsdFTKHSANinZseWnPcX",
|
"Account": "ra5nK24KXen9AHvsdFTKHSANinZseWnPcX",
|
||||||
"Fee": "12",
|
"Fee": "12",
|
||||||
"Flags": "262144",
|
"Flags": 262144,
|
||||||
"LastLedgerSequence": 8007750,
|
"LastLedgerSequence": 8007750,
|
||||||
"LimitAmount": {
|
"LimitAmount": {
|
||||||
"$type": "amount.token",
|
"$type": "json",
|
||||||
"$value": {
|
"$value": {
|
||||||
"currency": "USD",
|
"currency": "USD",
|
||||||
"issuer": "rsP3mgGb2tcYUrxiLFiHJiQXhsziegtwBc",
|
"issuer": "rsP3mgGb2tcYUrxiLFiHJiQXhsziegtwBc",
|
||||||
@@ -284,14 +243,5 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"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$/, /hook-bundle\.js$/],
|
test: /\.md$/,
|
||||||
use: 'raw-loader'
|
use: 'raw-loader'
|
||||||
})
|
})
|
||||||
return config
|
return config
|
||||||
|
|||||||
10
package.json
10
package.json
@@ -8,8 +8,7 @@
|
|||||||
"start": "next start",
|
"start": "next start",
|
||||||
"lint": "next lint",
|
"lint": "next lint",
|
||||||
"format": "prettier --write .",
|
"format": "prettier --write .",
|
||||||
"postinstall": "patch-package && yarn run postinstall-postinstall",
|
"postinstall": "patch-package"
|
||||||
"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",
|
||||||
@@ -65,9 +64,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.30",
|
"wabt": "1.0.16",
|
||||||
"xrpl-accountlib": "^1.6.1",
|
"xrpl-accountlib": "^1.5.2",
|
||||||
"xrpl-client": "^2.0.2"
|
"xrpl-client": "^1.9.4"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@types/dinero.js": "^1.9.0",
|
"@types/dinero.js": "^1.9.0",
|
||||||
@@ -76,7 +75,6 @@
|
|||||||
"@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,11 +10,10 @@ 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, { IFile } from '../../state'
|
import state 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
|
||||||
@@ -148,9 +147,6 @@ 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"
|
||||||
@@ -163,7 +159,7 @@ const Home: NextPage = () => {
|
|||||||
>
|
>
|
||||||
<main style={{ display: 'flex', flex: 1, position: 'relative' }}>
|
<main style={{ display: 'flex', flex: 1, position: 'relative' }}>
|
||||||
<HooksEditor />
|
<HooksEditor />
|
||||||
{canCompile && (
|
{snap.files[snap.active]?.name?.split('.')?.[1]?.toLowerCase() === 'c' && (
|
||||||
<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)}
|
||||||
@@ -197,7 +193,7 @@ const Home: NextPage = () => {
|
|||||||
</Flex>
|
</Flex>
|
||||||
</Hotkeys>
|
</Hotkeys>
|
||||||
)}
|
)}
|
||||||
{activeFileExt === 'js' && (
|
{snap.files[snap.active]?.name?.split('.')?.[1]?.toLowerCase() === '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)}
|
||||||
@@ -213,7 +209,7 @@ const Home: NextPage = () => {
|
|||||||
gap: '$2'
|
gap: '$2'
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<RunScript file={activeFile as IFile} />
|
<RunScript file={snap.files[snap.active]} />
|
||||||
</Flex>
|
</Flex>
|
||||||
</Hotkeys>
|
</Hotkeys>
|
||||||
)}
|
)}
|
||||||
@@ -229,7 +225,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>
|
||||||
{activeFileExt === 'js' && (
|
{snap.files[snap.active]?.name?.split('.')?.[1]?.toLowerCase() === 'js' && (
|
||||||
<Flex
|
<Flex
|
||||||
css={{
|
css={{
|
||||||
flex: 1
|
flex: 1
|
||||||
|
|||||||
@@ -68,6 +68,7 @@ const Test = () => {
|
|||||||
justifyContent: 'center',
|
justifyContent: 'center',
|
||||||
p: '$3 $2'
|
p: '$3 $2'
|
||||||
}}
|
}}
|
||||||
|
className="split-mobile-forceAutoHeight"
|
||||||
>
|
>
|
||||||
<Split
|
<Split
|
||||||
direction="horizontal"
|
direction="horizontal"
|
||||||
@@ -82,6 +83,7 @@ const Test = () => {
|
|||||||
height: '100%'
|
height: '100%'
|
||||||
}}
|
}}
|
||||||
onDragEnd={e => saveSplit('testHorizontal', e)}
|
onDragEnd={e => saveSplit('testHorizontal', e)}
|
||||||
|
className="split-mobile-forceVertical"
|
||||||
>
|
>
|
||||||
<Box css={{ width: '55%', px: '$2' }}>
|
<Box css={{ width: '55%', px: '$2' }}>
|
||||||
<Tabs
|
<Tabs
|
||||||
@@ -105,7 +107,18 @@ const Test = () => {
|
|||||||
))}
|
))}
|
||||||
</Tabs>
|
</Tabs>
|
||||||
</Box>
|
</Box>
|
||||||
<Box css={{ width: '45%', mx: '$2', height: '100%' }}>
|
<Box
|
||||||
|
css={{
|
||||||
|
width: '45%',
|
||||||
|
mx: '$0',
|
||||||
|
mt: '$1',
|
||||||
|
height: '100%',
|
||||||
|
'@md': {
|
||||||
|
mx: '$2',
|
||||||
|
mt: '$0'
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
>
|
||||||
<Accounts card hideDeployBtn showHookStats />
|
<Accounts card hideDeployBtn showHookStats />
|
||||||
</Box>
|
</Box>
|
||||||
</Split>
|
</Split>
|
||||||
@@ -131,6 +144,7 @@ const Test = () => {
|
|||||||
<Flex>
|
<Flex>
|
||||||
<Split
|
<Split
|
||||||
direction="horizontal"
|
direction="horizontal"
|
||||||
|
className="split-mobile-forceVertical"
|
||||||
sizes={[50, 50]}
|
sizes={[50, 50]}
|
||||||
minSize={[320, 160]}
|
minSize={[320, 160]}
|
||||||
gutterSize={4}
|
gutterSize={4}
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
5
raw-loader.d.ts
vendored
5
raw-loader.d.ts
vendored
@@ -2,8 +2,3 @@ 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
|
|
||||||
}
|
|
||||||
@@ -1,6 +1,5 @@
|
|||||||
import toast from 'react-hot-toast'
|
import toast from 'react-hot-toast'
|
||||||
import state, { FaucetAccountRes } from '../index'
|
import state, { FaucetAccountRes } from '../index'
|
||||||
import fetchAccountInfo from '../../utils/accountInfo';
|
|
||||||
|
|
||||||
export const names = [
|
export const names = [
|
||||||
'Alice',
|
'Alice',
|
||||||
@@ -36,37 +35,40 @@ export const addFaucetAccount = async (name?: string, showToast: boolean = false
|
|||||||
})
|
})
|
||||||
const json: FaucetAccountRes | { error: string } = await res.json()
|
const json: FaucetAccountRes | { error: string } = await res.json()
|
||||||
if ('error' in json) {
|
if ('error' in json) {
|
||||||
if (!showToast) return;
|
if (showToast) {
|
||||||
return toast.error(json.error, { id: toastId })
|
return toast.error(json.error, { id: toastId })
|
||||||
}
|
} else {
|
||||||
const currNames = state.accounts.map(acc => acc.name)
|
return
|
||||||
const info = await fetchAccountInfo(json.address, { silent: true })
|
}
|
||||||
state.accounts.push({
|
} else {
|
||||||
name: name || names.filter(name => !currNames.includes(name))[0],
|
if (showToast) {
|
||||||
xrp: (json.xrp || 0 * 1000000).toString(),
|
toast.success('New account created', { id: toastId })
|
||||||
address: json.address,
|
}
|
||||||
secret: json.secret,
|
const currNames = state.accounts.map(acc => acc.name)
|
||||||
sequence: info?.Sequence || 1,
|
state.accounts.push({
|
||||||
hooks: [],
|
name: name || names.filter(name => !currNames.includes(name))[0],
|
||||||
isLoading: false,
|
xrp: (json.xrp || 0 * 1000000).toString(),
|
||||||
version: '2'
|
address: json.address,
|
||||||
})
|
secret: json.secret,
|
||||||
if (showToast) {
|
sequence: 1,
|
||||||
toast.success('New account created', { id: toastId })
|
hooks: [],
|
||||||
|
isLoading: false,
|
||||||
|
version: '2'
|
||||||
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// fetch initial faucets
|
// fetch initial faucets
|
||||||
; (async function fetchFaucets() {
|
;(async function fetchFaucets() {
|
||||||
if (typeof window !== 'undefined') {
|
if (typeof window !== 'undefined') {
|
||||||
if (state.accounts.length === 0) {
|
if (state.accounts.length === 0) {
|
||||||
await addFaucetAccount()
|
await addFaucetAccount()
|
||||||
// setTimeout(() => {
|
// setTimeout(() => {
|
||||||
// addFaucetAccount();
|
// addFaucetAccount();
|
||||||
// }, 10000);
|
// }, 10000);
|
||||||
}
|
|
||||||
}
|
}
|
||||||
})()
|
}
|
||||||
|
})()
|
||||||
|
|
||||||
export const addFunds = async (address: string) => {
|
export const addFunds = async (address: string) => {
|
||||||
const toastId = toast.loading('Requesting funds')
|
const toastId = toast.loading('Requesting funds')
|
||||||
|
|||||||
@@ -15,11 +15,6 @@ 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!')
|
||||||
}
|
}
|
||||||
@@ -31,6 +26,7 @@ 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
|
||||||
@@ -76,7 +72,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 (await import('wabt')).default()
|
const ww = (await import('wabt')).default()
|
||||||
const myModule = ww.readWasm(new Uint8Array(bufferData), {
|
const myModule = ww.readWasm(new Uint8Array(bufferData), {
|
||||||
readDebugNames: true
|
readDebugNames: true
|
||||||
})
|
})
|
||||||
@@ -126,46 +122,3 @@ 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,19 +1,21 @@
|
|||||||
import { getFileExtention } from '../../utils/helpers'
|
|
||||||
import state, { IFile } from '../index'
|
import state, { IFile } from '../index'
|
||||||
|
|
||||||
const languageMapping: Record<string, string | undefined> = {
|
const languageMapping = {
|
||||||
ts: 'typescript',
|
ts: 'typescript',
|
||||||
js: 'javascript',
|
js: 'javascript',
|
||||||
md: 'markdown',
|
md: 'markdown',
|
||||||
c: 'c',
|
c: 'c',
|
||||||
h: 'c',
|
h: 'c',
|
||||||
txt: 'text'
|
other: ''
|
||||||
}
|
} /* Initializes empty file to global state */
|
||||||
|
|
||||||
export const createNewFile = (name: string) => {
|
export const createNewFile = (name: string) => {
|
||||||
const ext = getFileExtention(name) || ''
|
const tempName = name.split('.')
|
||||||
|
const fileExt = tempName[tempName.length - 1] || 'other'
|
||||||
const emptyFile: IFile = { name, language: languageMapping[ext] || 'text', content: '' }
|
const emptyFile: IFile = {
|
||||||
|
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
|
||||||
}
|
}
|
||||||
@@ -22,8 +24,5 @@ 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
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -14,4 +14,11 @@ export const deleteAccount = (addr?: string) => {
|
|||||||
if (!acc) return
|
if (!acc) return
|
||||||
acc.label = acc.value
|
acc.label = acc.value
|
||||||
})
|
})
|
||||||
|
transactionsState.transactions
|
||||||
|
.filter(t => t.state.selectedDestAccount?.value === addr)
|
||||||
|
.forEach(t => {
|
||||||
|
const acc = t.state.selectedDestAccount
|
||||||
|
if (!acc) return
|
||||||
|
acc.label = acc.value
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,9 +6,8 @@ import calculateHookOn, { TTS } from '../../utils/hookOnCalculator'
|
|||||||
import { Link } from '../../components'
|
import { Link } from '../../components'
|
||||||
import { ref } from 'valtio'
|
import { ref } from 'valtio'
|
||||||
import estimateFee from '../../utils/estimateFee'
|
import estimateFee from '../../utils/estimateFee'
|
||||||
import { SetHookData, toHex } from '../../utils/setHook'
|
import { SetHookData } from '../../utils/setHook'
|
||||||
import ResultLink from '../../components/ResultLink'
|
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)
|
||||||
@@ -18,6 +17,13 @@ export const sha256 = async (string: string) => {
|
|||||||
return hashHex
|
return hashHex
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function toHex(str: string) {
|
||||||
|
var result = ''
|
||||||
|
for (var i = 0; i < str.length; i++) {
|
||||||
|
result += str.charCodeAt(i).toString(16)
|
||||||
|
}
|
||||||
|
return result.toUpperCase()
|
||||||
|
}
|
||||||
|
|
||||||
function arrayBufferToHex(arrayBuffer?: ArrayBuffer | null) {
|
function arrayBufferToHex(arrayBuffer?: ArrayBuffer | null) {
|
||||||
if (!arrayBuffer) {
|
if (!arrayBuffer) {
|
||||||
@@ -58,6 +64,9 @@ 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
|
||||||
@@ -78,184 +87,196 @@ export const prepareDeployHookTx = async (
|
|||||||
// }
|
// }
|
||||||
// }
|
// }
|
||||||
// });
|
// });
|
||||||
if (typeof window === 'undefined') 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: data.Fee,
|
Fee: data.Fee,
|
||||||
NetworkID: process.env.NEXT_PUBLIC_NETWORK_ID,
|
Hooks: [
|
||||||
Hooks: [
|
{
|
||||||
{
|
Hook: {
|
||||||
Hook: {
|
CreateCode: arrayBufferToHex(activeFile?.compiledContent).toUpperCase(),
|
||||||
CreateCode: arrayBufferToHex(activeFile?.compiledContent).toUpperCase(),
|
HookOn: calculateHookOn(hookOnValues),
|
||||||
HookOn: calculateHookOn(hookOnValues),
|
HookNamespace,
|
||||||
HookNamespace,
|
HookApiVersion: 0,
|
||||||
HookApiVersion: 0,
|
Flags: 1,
|
||||||
Flags: 1,
|
// ...(filteredHookGrants.length > 0 && { HookGrants: filteredHookGrants }),
|
||||||
// ...(filteredHookGrants.length > 0 && { HookGrants: filteredHookGrants }),
|
...(filteredHookParameters.length > 0 && {
|
||||||
...(filteredHookParameters.length > 0 && {
|
HookParameters: filteredHookParameters
|
||||||
HookParameters: filteredHookParameters
|
})
|
||||||
})
|
}
|
||||||
}
|
}
|
||||||
}
|
]
|
||||||
]
|
}
|
||||||
|
return tx
|
||||||
}
|
}
|
||||||
return tx
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/* deployHook function turns the wasm binary into
|
||||||
* Turns the wasm binary into hex string, signs the transaction and deploys it to Hooks testnet.
|
* hex string, signs the transaction and deploys it to
|
||||||
|
* Hooks testnet.
|
||||||
*/
|
*/
|
||||||
export const deployHook = async (account: IAccount & { name?: string }, data: SetHookData) => {
|
export const deployHook = async (account: IAccount & { name?: string }, data: SetHookData) => {
|
||||||
const activeFile = state.files[state.active]?.compiledContent
|
if (typeof window !== 'undefined') {
|
||||||
? state.files[state.active]
|
const activeFile = state.files[state.active]?.compiledContent
|
||||||
: state.files.filter(file => file.compiledContent)[0]
|
? state.files[state.active]
|
||||||
state.deployValues[activeFile.name] = data
|
: state.files.filter(file => file.compiledContent)[0]
|
||||||
|
state.deployValues[activeFile.name] = data
|
||||||
|
const tx = await prepareDeployHookTx(account, data)
|
||||||
|
if (!tx) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if (!state.client) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
const keypair = derive.familySeed(account.secret)
|
||||||
|
|
||||||
const tx = await prepareDeployHookTx(account, data)
|
const { signedTransaction } = sign(tx, keypair)
|
||||||
if (!tx) {
|
const currentAccount = state.accounts.find(acc => acc.address === account.address)
|
||||||
return
|
if (currentAccount) {
|
||||||
}
|
currentAccount.isLoading = true
|
||||||
const keypair = derive.familySeed(account.secret)
|
}
|
||||||
const { signedTransaction } = sign(tx, keypair)
|
let submitRes
|
||||||
|
|
||||||
const currentAccount = state.accounts.find(acc => acc.address === account.address)
|
try {
|
||||||
if (currentAccount) {
|
submitRes = await state.client?.send({
|
||||||
currentAccount.isLoading = true
|
command: 'submit',
|
||||||
}
|
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({
|
|
||||||
type: 'success',
|
const txHash = submitRes.tx_json?.hash
|
||||||
message: resultMsg
|
const resultMsg = ref(
|
||||||
})
|
<>
|
||||||
} else if (submitRes.engine_result) {
|
[<ResultLink result={submitRes.engine_result} />] {submitRes.engine_result_message}{' '}
|
||||||
|
{txHash && (
|
||||||
|
<>
|
||||||
|
Transaction hash:{' '}
|
||||||
|
<Link
|
||||||
|
as="a"
|
||||||
|
href={`https://${process.env.NEXT_PUBLIC_EXPLORER_URL}/${txHash}`}
|
||||||
|
target="_blank"
|
||||||
|
rel="noopener noreferrer"
|
||||||
|
>
|
||||||
|
{txHash}
|
||||||
|
</Link>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
if (submitRes.engine_result === 'tesSUCCESS') {
|
||||||
|
state.deployLogs.push({
|
||||||
|
type: 'success',
|
||||||
|
message: 'Hook deployed successfully ✅'
|
||||||
|
})
|
||||||
|
state.deployLogs.push({
|
||||||
|
type: 'success',
|
||||||
|
message: resultMsg
|
||||||
|
})
|
||||||
|
} else if (submitRes.engine_result) {
|
||||||
|
state.deployLogs.push({
|
||||||
|
type: 'error',
|
||||||
|
message: resultMsg
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
state.deployLogs.push({
|
||||||
|
type: 'error',
|
||||||
|
message: `[${submitRes.error}] ${submitRes.error_exception}`
|
||||||
|
})
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
console.log(err)
|
||||||
state.deployLogs.push({
|
state.deployLogs.push({
|
||||||
type: 'error',
|
type: 'error',
|
||||||
message: resultMsg
|
message: 'Error occurred while deploying'
|
||||||
})
|
|
||||||
} else {
|
|
||||||
state.deployLogs.push({
|
|
||||||
type: 'error',
|
|
||||||
message: `[${submitRes.error}] ${submitRes.error_exception}`
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
} catch (err) {
|
if (currentAccount) {
|
||||||
console.error(err)
|
currentAccount.isLoading = false
|
||||||
state.deployLogs.push({
|
}
|
||||||
type: 'error',
|
return submitRes
|
||||||
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
|
||||||
}
|
}
|
||||||
const tx = {
|
if (typeof window !== 'undefined') {
|
||||||
Account: account.address,
|
const tx = {
|
||||||
TransactionType: 'SetHook',
|
Account: account.address,
|
||||||
Sequence: account.sequence,
|
TransactionType: 'SetHook',
|
||||||
Fee: '100000',
|
Sequence: account.sequence,
|
||||||
NetworkID: process.env.NEXT_PUBLIC_NETWORK_ID,
|
Fee: '100000',
|
||||||
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 || '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') {
|
const keypair = derive.familySeed(account.secret)
|
||||||
toast.success('Hook deleted successfully ✅', { id: toastId })
|
try {
|
||||||
state.deployLogs.push({
|
// Update tx Fee value with network estimation
|
||||||
type: 'success',
|
const res = await estimateFee(tx, account)
|
||||||
message: 'Hook deleted successfully ✅'
|
tx['Fee'] = res?.base_fee ? res?.base_fee : '1000'
|
||||||
})
|
} catch (err) {
|
||||||
state.deployLogs.push({
|
// use default value what you defined earlier
|
||||||
type: 'success',
|
console.log(err)
|
||||||
message: `[${submitRes.engine_result}] ${submitRes.engine_result_message} Validated ledger index: ${submitRes.validated_ledger_index}`
|
}
|
||||||
})
|
const { signedTransaction } = sign(tx, keypair)
|
||||||
currentAccount.hooks = []
|
|
||||||
} else {
|
if (currentAccount) {
|
||||||
toast.error(`${submitRes.engine_result_message || submitRes.error_exception}`, {
|
currentAccount.isLoading = true
|
||||||
id: toastId
|
}
|
||||||
|
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 })
|
||||||
state.deployLogs.push({
|
state.deployLogs.push({
|
||||||
type: 'error',
|
type: 'error',
|
||||||
message: `[${submitRes.engine_result || submitRes.error}] ${
|
message: 'Error occurred while deleting hook'
|
||||||
submitRes.engine_result_message || submitRes.error_exception
|
|
||||||
}`
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
} catch (err) {
|
if (currentAccount) {
|
||||||
console.log(err)
|
currentAccount.isLoading = false
|
||||||
toast.error('Error occurred while deleting hook', { id: toastId })
|
}
|
||||||
state.deployLogs.push({
|
return submitRes
|
||||||
type: 'error',
|
|
||||||
message: 'Error occurred while deleting hook'
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
if (currentAccount) {
|
|
||||||
currentAccount.isLoading = false
|
|
||||||
}
|
|
||||||
return submitRes
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -61,7 +61,6 @@ 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
|
||||||
}
|
}
|
||||||
|
|||||||
66
state/actions/sendTransaction.ts
Normal file
66
state/actions/sendTransaction.ts
Normal file
@@ -0,0 +1,66 @@
|
|||||||
|
import { derive, sign } from 'xrpl-accountlib'
|
||||||
|
|
||||||
|
import state from '..'
|
||||||
|
import type { IAccount } from '..'
|
||||||
|
|
||||||
|
interface TransactionOptions {
|
||||||
|
TransactionType: string
|
||||||
|
Account?: string
|
||||||
|
Fee?: string
|
||||||
|
Destination?: string
|
||||||
|
[index: string]: any
|
||||||
|
}
|
||||||
|
interface OtherOptions {
|
||||||
|
logPrefix?: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export const sendTransaction = async (
|
||||||
|
account: IAccount,
|
||||||
|
txOptions: TransactionOptions,
|
||||||
|
options?: OtherOptions
|
||||||
|
) => {
|
||||||
|
if (!state.client) throw Error('XRPL client not initalized')
|
||||||
|
|
||||||
|
const { Fee = '1000', ...opts } = txOptions
|
||||||
|
const tx: TransactionOptions = {
|
||||||
|
Account: account.address,
|
||||||
|
Sequence: account.sequence,
|
||||||
|
Fee, // TODO auto-fillable default
|
||||||
|
...opts
|
||||||
|
}
|
||||||
|
const { logPrefix = '' } = options || {}
|
||||||
|
try {
|
||||||
|
const signedAccount = derive.familySeed(account.secret)
|
||||||
|
const { signedTransaction } = sign(tx, signedAccount)
|
||||||
|
const response = await state.client.send({
|
||||||
|
command: 'submit',
|
||||||
|
tx_blob: signedTransaction
|
||||||
|
})
|
||||||
|
if (response.engine_result === 'tesSUCCESS') {
|
||||||
|
state.transactionLogs.push({
|
||||||
|
type: 'success',
|
||||||
|
message: `${logPrefix}[${response.engine_result}] ${response.engine_result_message}`
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
state.transactionLogs.push({
|
||||||
|
type: 'error',
|
||||||
|
message: `${logPrefix}[${response.error || response.engine_result}] ${
|
||||||
|
response.error_exception || response.engine_result_message
|
||||||
|
}`
|
||||||
|
})
|
||||||
|
}
|
||||||
|
const currAcc = state.accounts.find(acc => acc.address === account.address)
|
||||||
|
if (currAcc && response.account_sequence_next) {
|
||||||
|
currAcc.sequence = response.account_sequence_next
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
console.error(err)
|
||||||
|
state.transactionLogs.push({
|
||||||
|
type: 'error',
|
||||||
|
message:
|
||||||
|
err instanceof Error
|
||||||
|
? `${logPrefix}Error: ${err.message}`
|
||||||
|
: `${logPrefix}Something went wrong, try again later`
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -4,12 +4,12 @@ import state from '..'
|
|||||||
import type { IAccount } from '..'
|
import type { IAccount } from '..'
|
||||||
import ResultLink from '../../components/ResultLink'
|
import ResultLink from '../../components/ResultLink'
|
||||||
import { ref } from 'valtio'
|
import { ref } from 'valtio'
|
||||||
import { xrplSend } from './xrpl-client'
|
|
||||||
|
|
||||||
interface TransactionOptions {
|
interface TransactionOptions {
|
||||||
TransactionType: string
|
TransactionType: string
|
||||||
Account?: string
|
Account?: string
|
||||||
Fee?: string
|
Fee?: string
|
||||||
|
Destination?: string
|
||||||
[index: string]: any
|
[index: string]: any
|
||||||
}
|
}
|
||||||
interface OtherOptions {
|
interface OtherOptions {
|
||||||
@@ -21,23 +21,20 @@ 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,
|
Fee, // TODO auto-fillable default
|
||||||
NetworkID: process.env.NEXT_PUBLIC_NETWORK_ID,
|
|
||||||
...opts
|
...opts
|
||||||
}
|
}
|
||||||
const { logPrefix = '' } = options || {}
|
const { logPrefix = '' } = options || {}
|
||||||
state.transactionLogs.push({
|
|
||||||
type: 'log',
|
|
||||||
message: `${logPrefix}${JSON.stringify(tx, null, 2)}`
|
|
||||||
})
|
|
||||||
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 xrplSend({
|
const response = await state.client.send({
|
||||||
command: 'submit',
|
command: 'submit',
|
||||||
tx_blob: signedTransaction
|
tx_blob: signedTransaction
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -1,7 +0,0 @@
|
|||||||
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);
|
|
||||||
}
|
|
||||||
@@ -1,79 +0,0 @@
|
|||||||
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
|
client: XrplClient | null
|
||||||
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: undefined!, // set below only.
|
client: null,
|
||||||
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,56 +4,33 @@ 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'
|
|
||||||
import { fromHex } from '../utils/setHook'
|
|
||||||
import { typeIs } from '../utils/helpers'
|
|
||||||
|
|
||||||
export type SelectOption = {
|
export type SelectOption = {
|
||||||
value: string
|
value: string
|
||||||
label: string
|
label: string
|
||||||
}
|
}
|
||||||
|
|
||||||
export type HookParameters = {
|
|
||||||
[key: string]: SelectOption
|
|
||||||
}
|
|
||||||
|
|
||||||
export type Memos = {
|
|
||||||
[key: string]: {
|
|
||||||
type: string
|
|
||||||
format: string
|
|
||||||
data: string
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface TransactionState {
|
export interface TransactionState {
|
||||||
selectedTransaction: SelectOption | null
|
selectedTransaction: SelectOption | null
|
||||||
selectedAccount: SelectOption | null
|
selectedAccount: SelectOption | null
|
||||||
selectedFlags: SelectOption[] | null
|
selectedDestAccount: SelectOption | null
|
||||||
hookParameters: HookParameters
|
|
||||||
memos: Memos
|
|
||||||
txIsLoading: boolean
|
txIsLoading: boolean
|
||||||
txIsDisabled: boolean
|
txIsDisabled: boolean
|
||||||
txFields: TxFields
|
txFields: TxFields
|
||||||
viewType: 'json' | 'ui'
|
viewType: 'json' | 'ui'
|
||||||
editorValue?: string
|
editorValue?: string
|
||||||
editorIsSaved: boolean
|
|
||||||
estimatedFee?: string
|
estimatedFee?: string
|
||||||
}
|
}
|
||||||
|
|
||||||
const commonFields = ['TransactionType', 'Account', 'Sequence', "HookParameters"] as const;
|
|
||||||
|
|
||||||
export type TxFields = Omit<
|
export type TxFields = Omit<
|
||||||
Partial<typeof transactionsData[0]>,
|
Partial<typeof transactionsData[0]>,
|
||||||
typeof commonFields[number]
|
'Account' | 'Sequence' | 'TransactionType'
|
||||||
>
|
>
|
||||||
|
|
||||||
export const defaultTransaction: TransactionState = {
|
export const defaultTransaction: TransactionState = {
|
||||||
selectedTransaction: null,
|
selectedTransaction: null,
|
||||||
selectedAccount: null,
|
selectedAccount: null,
|
||||||
selectedFlags: null,
|
selectedDestAccount: null,
|
||||||
hookParameters: {},
|
|
||||||
memos: {},
|
|
||||||
editorIsSaved: true,
|
|
||||||
txIsLoading: false,
|
txIsLoading: false,
|
||||||
txIsDisabled: false,
|
txIsDisabled: false,
|
||||||
txFields: {},
|
txFields: {},
|
||||||
@@ -129,51 +106,47 @@ export const modifyTxState = (
|
|||||||
return tx.state
|
return tx.state
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// state to tx options
|
||||||
export const prepareTransaction = (data: any) => {
|
export const prepareTransaction = (data: any) => {
|
||||||
let options = { ...data }
|
let options = { ...data }
|
||||||
|
|
||||||
Object.keys(options).forEach(field => {
|
Object.keys(options).forEach(field => {
|
||||||
let _value = options[field]
|
let _value = options[field]
|
||||||
if (!typeIs(_value, 'object')) return
|
// convert xrp
|
||||||
// amount.xrp
|
if (_value && typeof _value === 'object' && _value.$type === 'xrp') {
|
||||||
if (_value.$type === 'amount.xrp') {
|
if (+_value.$value) {
|
||||||
if (_value.$value) {
|
options[field] = (+_value.$value * 1000000 + '') as any
|
||||||
options[field] = (+(_value as any).$value * 1000000 + '')
|
|
||||||
} else {
|
} else {
|
||||||
options[field] = ""
|
options[field] = undefined // 👇 💀
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// amount.token
|
// handle type: `json`
|
||||||
if (_value.$type === 'amount.token') {
|
if (_value && typeof _value === 'object' && _value.$type === 'json') {
|
||||||
if (typeIs(_value.$value, 'string')) {
|
if (typeof _value.$value === 'object') {
|
||||||
options[field] = parseJSON(_value.$value)
|
|
||||||
} else if (typeIs(_value.$value, 'object')) {
|
|
||||||
options[field] = _value.$value
|
options[field] = _value.$value
|
||||||
} else {
|
} else {
|
||||||
options[field] = undefined
|
try {
|
||||||
|
options[field] = JSON.parse(_value.$value)
|
||||||
|
} catch (error) {
|
||||||
|
const message = `Input error for json field '${field}': ${
|
||||||
|
error instanceof Error ? error.message : ''
|
||||||
|
}`
|
||||||
|
console.error(message)
|
||||||
|
options[field] = _value.$value
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// account
|
|
||||||
if (_value.$type === 'account') {
|
// delete unnecessary fields
|
||||||
options[field] = (_value.$value as any)?.toString() || ""
|
if (!options[field]) {
|
||||||
}
|
delete options[field]
|
||||||
// json
|
|
||||||
if (_value.$type === 'json') {
|
|
||||||
const val = _value.$value;
|
|
||||||
let res: any = val;
|
|
||||||
if (typeIs(val, ["object", "array"])) {
|
|
||||||
options[field] = res
|
|
||||||
} else if (typeIs(val, "string") && (res = parseJSON(val))) {
|
|
||||||
options[field] = res;
|
|
||||||
} else {
|
|
||||||
options[field] = res;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
return options
|
return options
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// editor value to state
|
||||||
export const prepareState = (value: string, transactionType?: string) => {
|
export const prepareState = (value: string, transactionType?: string) => {
|
||||||
const options = parseJSON(value)
|
const options = parseJSON(value)
|
||||||
if (!options) {
|
if (!options) {
|
||||||
@@ -183,7 +156,7 @@ export const prepareState = (value: string, transactionType?: string) => {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
const { Account, TransactionType, HookParameters, Memos, ...rest } = options
|
const { Account, TransactionType, Destination, ...rest } = options
|
||||||
let tx: Partial<TransactionState> = {}
|
let tx: Partial<TransactionState> = {}
|
||||||
const schema = getTxFields(transactionType)
|
const schema = getTxFields(transactionType)
|
||||||
|
|
||||||
@@ -213,58 +186,39 @@ export const prepareState = (value: string, transactionType?: string) => {
|
|||||||
tx.selectedTransaction = null
|
tx.selectedTransaction = null
|
||||||
}
|
}
|
||||||
|
|
||||||
if (HookParameters && HookParameters instanceof Array) {
|
if (schema.Destination !== undefined) {
|
||||||
tx.hookParameters = HookParameters.reduce<TransactionState["hookParameters"]>((acc, cur, idx) => {
|
const dest = state.accounts.find(acc => acc.address === Destination)
|
||||||
const param = { label: fromHex(cur.HookParameter?.HookParameterName || ""), value: fromHex(cur.HookParameter?.HookParameterValue || "") }
|
if (dest) {
|
||||||
acc[idx] = param;
|
tx.selectedDestAccount = {
|
||||||
return acc;
|
label: dest.name,
|
||||||
}, {})
|
value: dest.address
|
||||||
}
|
}
|
||||||
|
} else if (Destination) {
|
||||||
if (Memos && Memos instanceof Array) {
|
tx.selectedDestAccount = {
|
||||||
tx.memos = Memos.reduce<TransactionState["memos"]>((acc, cur, idx) => {
|
label: Destination,
|
||||||
const memo = { data: fromHex(cur.Memo?.MemoData || ""), type: fromHex(cur.Memo?.MemoType || ""), format: fromHex(cur.Memo?.MemoFormat || "") }
|
value: Destination
|
||||||
acc[idx] = memo;
|
}
|
||||||
return acc;
|
} else {
|
||||||
}, {})
|
tx.selectedDestAccount = null
|
||||||
}
|
}
|
||||||
|
} else if (Destination) {
|
||||||
|
rest.Destination = Destination
|
||||||
if (getFlags(TransactionType) && rest.Flags) {
|
|
||||||
const flags = extractFlags(TransactionType, rest.Flags)
|
|
||||||
|
|
||||||
rest.Flags = undefined
|
|
||||||
tx.selectedFlags = flags
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Object.keys(rest).forEach(field => {
|
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]
|
||||||
|
const isXrp =
|
||||||
const isAmount = schemaVal &&
|
typeof value !== 'object' &&
|
||||||
typeIs(schemaVal, "object") &&
|
schemaVal &&
|
||||||
schemaVal.$type.startsWith('amount.');
|
typeof schemaVal === 'object' &&
|
||||||
const isAccount = schemaVal &&
|
schemaVal.$type === 'xrp'
|
||||||
typeIs(schemaVal, "object") &&
|
if (isXrp) {
|
||||||
schemaVal.$type.startsWith("account");
|
|
||||||
|
|
||||||
if (isAmount && ["number", "string"].includes(typeof value)) {
|
|
||||||
rest[field] = {
|
rest[field] = {
|
||||||
$type: 'amount.xrp', // TODO narrow typed $type.
|
$type: 'xrp',
|
||||||
$value: +value / 1000000 // ! maybe use bigint?
|
$value: +value / 1000000 // ! maybe use bigint?
|
||||||
}
|
}
|
||||||
} else if (isAmount && typeof value === 'object') {
|
} else if (typeof value === 'object') {
|
||||||
rest[field] = {
|
|
||||||
$type: 'amount.token',
|
|
||||||
$value: value
|
|
||||||
}
|
|
||||||
} else if (isAccount) {
|
|
||||||
rest[field] = {
|
|
||||||
$type: "account",
|
|
||||||
$value: value?.toString() || ""
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else if (typeof value === 'object') {
|
|
||||||
rest[field] = {
|
rest[field] = {
|
||||||
$type: 'json',
|
$type: 'json',
|
||||||
$value: value
|
$value: value
|
||||||
@@ -273,7 +227,6 @@ export const prepareState = (value: string, transactionType?: string) => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
tx.txFields = rest
|
tx.txFields = rest
|
||||||
tx.editorIsSaved = true;
|
|
||||||
|
|
||||||
return tx
|
return tx
|
||||||
}
|
}
|
||||||
@@ -284,12 +237,12 @@ export const getTxFields = (tt?: string) => {
|
|||||||
if (!txFields) return {}
|
if (!txFields) return {}
|
||||||
|
|
||||||
let _txFields = Object.keys(txFields)
|
let _txFields = Object.keys(txFields)
|
||||||
.filter(key => !commonFields.includes(key as any))
|
.filter(key => !['TransactionType', 'Account', 'Sequence'].includes(key))
|
||||||
.reduce<TxFields>((tf, key) => ((tf[key as keyof TxFields] = (txFields as any)[key]), tf), {})
|
.reduce<TxFields>((tf, key) => ((tf[key as keyof TxFields] = (txFields as any)[key]), tf), {})
|
||||||
return _txFields
|
return _txFields
|
||||||
}
|
}
|
||||||
|
|
||||||
export { transactionsData, commonFields }
|
export { transactionsData }
|
||||||
|
|
||||||
export const transactionsOptions = transactionsData.map(tx => ({
|
export const transactionsOptions = transactionsData.map(tx => ({
|
||||||
value: tx.TransactionType,
|
value: tx.TransactionType,
|
||||||
|
|||||||
@@ -54,3 +54,15 @@ html.light .gutter-horizontal:hover {
|
|||||||
.monaco-editor .monaco-hover {
|
.monaco-editor .monaco-hover {
|
||||||
z-index: 9999;
|
z-index: 9999;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@media screen and (max-width: 48rem) {
|
||||||
|
.split-mobile-forceAutoHeight {
|
||||||
|
height: auto !important;
|
||||||
|
}
|
||||||
|
.split-mobile-forceVertical {
|
||||||
|
flex-direction: column !important;
|
||||||
|
}
|
||||||
|
.split-mobile-forceVertical > div {
|
||||||
|
width: 100% !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,31 +0,0 @@
|
|||||||
import toast from 'react-hot-toast'
|
|
||||||
|
|
||||||
import { xrplSend } from '../state/actions/xrpl-client'
|
|
||||||
|
|
||||||
interface AccountInfo {
|
|
||||||
Account: string,
|
|
||||||
Sequence: number,
|
|
||||||
Flags: number,
|
|
||||||
Balance?: string,
|
|
||||||
}
|
|
||||||
|
|
||||||
const fetchAccountInfo = async (
|
|
||||||
address: string,
|
|
||||||
opts: { silent?: boolean } = {}
|
|
||||||
): Promise<AccountInfo | undefined> => {
|
|
||||||
try {
|
|
||||||
const res = await xrplSend({
|
|
||||||
id: `hooks-builder-req-info-${address}`,
|
|
||||||
command: 'account_info',
|
|
||||||
account: address
|
|
||||||
})
|
|
||||||
return res.account_data;
|
|
||||||
} catch (err) {
|
|
||||||
if (!opts.silent) {
|
|
||||||
console.error(err)
|
|
||||||
toast.error('Could not fetch account info!')
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export default fetchAccountInfo
|
|
||||||
@@ -1,7 +1,6 @@
|
|||||||
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 { IAccount } from '../state'
|
import state, { IAccount } from '../state'
|
||||||
import { xrplSend } from '../state/actions/xrpl-client'
|
|
||||||
|
|
||||||
const estimateFee = async (
|
const estimateFee = async (
|
||||||
tx: Record<string, unknown>,
|
tx: Record<string, unknown>,
|
||||||
@@ -23,10 +22,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 xrplSend({ command: 'fee', tx_blob: signedTransaction })
|
const res = await state.client?.send({ command: 'fee', tx_blob: signedTransaction })
|
||||||
if (res.error) {
|
|
||||||
throw new Error(`[${res.error}] ${res.error_exception}.`);
|
|
||||||
}
|
|
||||||
if (res && res.drops) {
|
if (res && res.drops) {
|
||||||
return res.drops
|
return res.drops
|
||||||
}
|
}
|
||||||
@@ -34,8 +30,7 @@ const estimateFee = async (
|
|||||||
} catch (err) {
|
} catch (err) {
|
||||||
if (!opts.silent) {
|
if (!opts.silent) {
|
||||||
console.error(err)
|
console.error(err)
|
||||||
const msg = err instanceof Error ? err.message : 'Error estimating fee!';
|
toast.error('Cannot estimate fee.') // ? Some better msg
|
||||||
toast.error(msg);
|
|
||||||
}
|
}
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -13,23 +13,3 @@ 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
|
|
||||||
}
|
|
||||||
|
|
||||||
type Type = "array" | "undefined" | "object" | "string" | "number" | "bigint" | "boolean" | "symbol" | "function"
|
|
||||||
type obj = Record<string | number | symbol, unknown>
|
|
||||||
type arr = unknown[]
|
|
||||||
|
|
||||||
export const typeIs = <T extends Type>(arg: any, t: T | T[]): arg is unknown & (T extends "array" ? arr : T extends "undefined" ? undefined | null : T extends "object" ? obj : T extends "string" ? string : T extends "number" ? number : T extends "bigint" ? bigint : T extends "boolean" ? boolean : T extends "symbol" ? symbol : T extends "function" ? Function : never) => {
|
|
||||||
const types = Array.isArray(t) ? t : [t]
|
|
||||||
return types.includes(typeOf(arg) as T);
|
|
||||||
}
|
|
||||||
|
|
||||||
export const typeOf = (arg: any): Type => {
|
|
||||||
const type = arg instanceof Array ? 'array' : arg === null ? 'undefined' : typeof arg
|
|
||||||
return type;
|
|
||||||
}
|
|
||||||
@@ -23,22 +23,23 @@ 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 s = '0x3e3ff5bf'
|
let start = '0x000000003e3ff5bf'
|
||||||
arr.forEach(n => {
|
arr.forEach(n => {
|
||||||
let v = BigInt(s)
|
let v = BigInt(start)
|
||||||
v ^= BigInt(1) << BigInt(tts[n])
|
v ^= BigInt(1) << BigInt(tts[n as keyof TTS])
|
||||||
s = "0x" + v.toString(16)
|
let s = v.toString(16)
|
||||||
|
let l = s.length
|
||||||
|
if (l < 16) s = '0'.repeat(16 - l) + s
|
||||||
|
s = '0x' + s
|
||||||
|
start = s
|
||||||
})
|
})
|
||||||
s = s.replace('0x', '')
|
return start.substring(2)
|
||||||
s = s.padStart(64, '0')
|
|
||||||
return s
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export default calculateHookOn
|
export default calculateHookOn
|
||||||
|
|||||||
59
utils/libwabt.js
Normal file
59
utils/libwabt.js
Normal file
File diff suppressed because one or more lines are too long
@@ -1,8 +1,9 @@
|
|||||||
import { typeIs, typeOf } from './helpers'
|
|
||||||
|
|
||||||
export const extractSchemaProps = <O extends object>(obj: O) =>
|
export const extractSchemaProps = <O extends object>(obj: O) =>
|
||||||
Object.entries(obj).reduce((prev, [key, val]) => {
|
Object.entries(obj).reduce((prev, [key, val]) => {
|
||||||
const value = typeIs(val, "object") && '$type' in val && '$value' in val ? val?.$value : val
|
const typeOf = <T>(arg: T) =>
|
||||||
|
arg instanceof Array ? 'array' : arg === null ? 'undefined' : typeof arg
|
||||||
|
|
||||||
|
const value = typeOf(val) === 'object' && '$type' in val && '$value' in val ? val?.$value : val
|
||||||
const type = typeOf(value)
|
const type = typeOf(value)
|
||||||
|
|
||||||
let schema: any = {
|
let schema: any = {
|
||||||
@@ -11,19 +12,19 @@ export const extractSchemaProps = <O extends object>(obj: O) =>
|
|||||||
default: value
|
default: value
|
||||||
}
|
}
|
||||||
|
|
||||||
if (typeIs(value, "array")) {
|
if (typeOf(value) === 'array') {
|
||||||
const item = value[0] // TODO merge other item schema's into one
|
const item = value[0] // TODO merge other item schema's into one
|
||||||
if (typeIs(item, "object")) {
|
if (typeOf(item) !== 'object') {
|
||||||
schema.items = {
|
schema.items = {
|
||||||
type: 'object',
|
type: 'object',
|
||||||
properties: extractSchemaProps(item),
|
properties: extractSchemaProps(item),
|
||||||
default: item
|
default: item
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// TODO primitive-value arrays
|
// TODO support primitive-value arrays
|
||||||
}
|
}
|
||||||
|
|
||||||
if (typeIs(value, "object")) {
|
if (typeOf(value) === 'object') {
|
||||||
schema.properties = extractSchemaProps(value)
|
schema.properties = extractSchemaProps(value)
|
||||||
}
|
}
|
||||||
return {
|
return {
|
||||||
|
|||||||
@@ -20,13 +20,6 @@ export type SetHookData = {
|
|||||||
}
|
}
|
||||||
$metaData?: any
|
$metaData?: any
|
||||||
}[]
|
}[]
|
||||||
Memos?: {
|
|
||||||
Memo: {
|
|
||||||
MemoType: string,
|
|
||||||
MemoData: string
|
|
||||||
MemoFormat: string
|
|
||||||
}
|
|
||||||
}[]
|
|
||||||
// HookGrants: {
|
// HookGrants: {
|
||||||
// HookGrant: {
|
// HookGrant: {
|
||||||
// Authorize: string;
|
// Authorize: string;
|
||||||
@@ -81,19 +74,3 @@ export const getInvokeOptions = (content?: string) => {
|
|||||||
|
|
||||||
return invokeOptions
|
return invokeOptions
|
||||||
}
|
}
|
||||||
|
|
||||||
export function toHex(str: string) {
|
|
||||||
var result = ''
|
|
||||||
for (var i = 0; i < str.length; i++) {
|
|
||||||
result += str.charCodeAt(i).toString(16)
|
|
||||||
}
|
|
||||||
return result.toUpperCase()
|
|
||||||
}
|
|
||||||
|
|
||||||
export function fromHex(hex: string) {
|
|
||||||
var str = ''
|
|
||||||
for (var i = 0; i < hex.length; i += 2) {
|
|
||||||
str += String.fromCharCode(parseInt(hex.substring(i, i + 2), 16))
|
|
||||||
}
|
|
||||||
return str
|
|
||||||
}
|
|
||||||
@@ -22,7 +22,6 @@ 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'
|
||||||
@@ -71,7 +70,6 @@ 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,
|
||||||
|
|||||||
@@ -1,6 +0,0 @@
|
|||||||
# 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,35 +1,14 @@
|
|||||||
# hooks-guard-in-for
|
# hooks-guard-in-for
|
||||||
|
|
||||||
Consider the following for-loop in C:
|
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:
|
||||||
|
|
||||||
```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)
|
||||||
```
|
```
|
||||||
|
|
||||||
To satisfy the guard rule when using a for-loop in C guard should be
|
<BR/>
|
||||||
placed either in the condition part of the loop, or as a first call in loop body, e.g.
|
This is the only way to satisfy the guard rule when using a for-loop in C.
|
||||||
|
|
||||||
```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