Compare commits
	
		
			44 Commits
		
	
	
		
			fix/nft-cr
			...
			feat/ts-su
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| 
						 | 
					513db7bf09 | ||
| 
						 | 
					969fd0f63c | ||
| 
						 | 
					412e3f2bbf | ||
| 
						 | 
					dc37b1911a | ||
| 
						 | 
					c348868c89 | ||
| 
						 | 
					2c3cfebe3a | ||
| 
						 | 
					6265a9cdbf | ||
| 
						 | 
					1321b498cf | ||
| 
						 | 
					801d9778cb | ||
| 
						 | 
					2cf18ef61c | ||
| 
						 | 
					4d2dc16ce5 | ||
| 
						 | 
					9ecf5478e6 | ||
| 
						 | 
					6d1ef110b7 | ||
| 
						 | 
					b9edfcd63b | ||
| 
						 | 
					b653d9a9cb | ||
| 
						 | 
					da28e0a7d1 | ||
| 
						 | 
					8a5b83d57f | ||
| 
						 | 
					025eff6cf2 | ||
| 
						 | 
					62d521b2cc | ||
| 
						 | 
					7aafca21df | ||
| 
						 | 
					80f58e903c | ||
| 
						 | 
					c4af3df017 | ||
| 
						 | 
					5d8d142bc4 | ||
| 
						 | 
					e27a71d713 | ||
| 
						 | 
					e08b07cbeb | ||
| 
						 | 
					e4936c03ef | ||
| 
						 | 
					21a69ac8ea | ||
| 
						 | 
					52e4f219f7 | ||
| 
						 | 
					bd1a0c9836 | ||
| 
						 | 
					ba95eb5be2 | ||
| 
						 | 
					6911bcc0f3 | ||
| 
						 | 
					a6055feb1f | ||
| 
						 | 
					31a185f49f | ||
| 
						 | 
					d693458421 | ||
| 
						 | 
					692bbeb9b2 | ||
| 
						 | 
					a29368d633 | ||
| 
						 | 
					0f32fcb71c | ||
| 
						 | 
					90db6b1f12 | ||
| 
						 | 
					dde0317544 | ||
| 
						 | 
					a6ad39f6e8 | ||
| 
						 | 
					0536e48cfe | ||
| 
						 | 
					a08823a38b | ||
| 
						 | 
					653e28583d | ||
| 
						 | 
					e75443c8e2 | 
							
								
								
									
										1
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										1
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							@@ -23,6 +23,7 @@
 | 
			
		||||
npm-debug.log*
 | 
			
		||||
yarn-debug.log*
 | 
			
		||||
yarn-error.log*
 | 
			
		||||
.yarn
 | 
			
		||||
 | 
			
		||||
# local env files
 | 
			
		||||
.env.local
 | 
			
		||||
 
 | 
			
		||||
@@ -94,7 +94,7 @@ const DeployEditor = () => {
 | 
			
		||||
      }}
 | 
			
		||||
    >
 | 
			
		||||
      {`You haven't compiled any files yet, compile files on `}
 | 
			
		||||
      <NextLink shallow href={`/develop/${router.query.slug}`} passHref>
 | 
			
		||||
      <NextLink legacyBehavior shallow href={`/develop/${router.query.slug}`} passHref>
 | 
			
		||||
        <Link as="a">develop view</Link>
 | 
			
		||||
      </NextLink>
 | 
			
		||||
    </Text>
 | 
			
		||||
 
 | 
			
		||||
@@ -155,9 +155,11 @@ const EditorNavigation = ({ renderNav }: { renderNav?: () => ReactNode }) => {
 | 
			
		||||
                >
 | 
			
		||||
                  <Image
 | 
			
		||||
                    src={session?.user?.image || ''}
 | 
			
		||||
                    width="30px"
 | 
			
		||||
                    height="30px"
 | 
			
		||||
                    objectFit="cover"
 | 
			
		||||
                    width="30"
 | 
			
		||||
                    height="30"
 | 
			
		||||
                    style={{
 | 
			
		||||
                      objectFit: 'cover'
 | 
			
		||||
                    }}
 | 
			
		||||
                    alt="User avatar"
 | 
			
		||||
                  />
 | 
			
		||||
                </Box>
 | 
			
		||||
 
 | 
			
		||||
@@ -40,7 +40,6 @@ const EnrichLog: FC<EnrichLogProps> = ({ str }) => {
 | 
			
		||||
      if (match.startsWith('HookSet')) {
 | 
			
		||||
        const code = match.match(/^HookSet\((\d+)\)/)?.[1]
 | 
			
		||||
        const val = hookSetCodes.find(v => code && v.code === +code)
 | 
			
		||||
        console.log({ code, val })
 | 
			
		||||
        if (!val) return match
 | 
			
		||||
 | 
			
		||||
        const content = capitalize(val.description) || 'No hint available!'
 | 
			
		||||
 
 | 
			
		||||
@@ -94,6 +94,14 @@ const setMarkers = (monacoE: typeof monaco) => {
 | 
			
		||||
  })
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
const langWarnings: Record<string, { shown: boolean; message: string }> = {
 | 
			
		||||
  ts: {
 | 
			
		||||
    shown: false,
 | 
			
		||||
    message:
 | 
			
		||||
      'Typescript suppport for hooks is still in early planning stage, write actual hooks in C only for now!'
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
const HooksEditor = () => {
 | 
			
		||||
  const editorRef = useRef<monaco.editor.IStandaloneCodeEditor>()
 | 
			
		||||
  const monacoRef = useRef<typeof monaco>()
 | 
			
		||||
@@ -125,6 +133,14 @@ const HooksEditor = () => {
 | 
			
		||||
 | 
			
		||||
  const file = snap.files[snap.active]
 | 
			
		||||
 | 
			
		||||
  useEffect(() => {
 | 
			
		||||
    let warning = langWarnings[file?.language || '']
 | 
			
		||||
    if (warning && !warning.shown) {
 | 
			
		||||
      alert(warning.message) // TODO Custom dialog.
 | 
			
		||||
      warning.shown = true
 | 
			
		||||
    }
 | 
			
		||||
  }, [file])
 | 
			
		||||
 | 
			
		||||
  const renderNav = () => (
 | 
			
		||||
    <Tabs
 | 
			
		||||
      label="File"
 | 
			
		||||
@@ -221,7 +237,7 @@ const HooksEditor = () => {
 | 
			
		||||
                monaco.languages.register({
 | 
			
		||||
                  id: 'text',
 | 
			
		||||
                  extensions: ['.txt'],
 | 
			
		||||
                  mimetypes: ['text/plain'],
 | 
			
		||||
                  mimetypes: ['text/plain']
 | 
			
		||||
                })
 | 
			
		||||
                MonacoServices.install(monaco)
 | 
			
		||||
                const webSocket = createWebSocket(
 | 
			
		||||
@@ -240,7 +256,7 @@ const HooksEditor = () => {
 | 
			
		||||
                      try {
 | 
			
		||||
                        disposable.dispose()
 | 
			
		||||
                      } catch (err) {
 | 
			
		||||
                        console.log('err', err)
 | 
			
		||||
                        console.error('err', err)
 | 
			
		||||
                      }
 | 
			
		||||
                    })
 | 
			
		||||
                  }
 | 
			
		||||
 
 | 
			
		||||
@@ -160,7 +160,7 @@ export const Log: FC<ILog> = ({
 | 
			
		||||
        )}
 | 
			
		||||
        <Pre>{message}</Pre>
 | 
			
		||||
        {link && (
 | 
			
		||||
          <NextLink href={link} shallow passHref>
 | 
			
		||||
          <NextLink legacyBehavior href={link} shallow passHref>
 | 
			
		||||
            <Link as="a">{linkText}</Link>
 | 
			
		||||
          </NextLink>
 | 
			
		||||
        )}
 | 
			
		||||
 
 | 
			
		||||
@@ -1,5 +1,5 @@
 | 
			
		||||
import React from 'react'
 | 
			
		||||
import Link from 'next/link'
 | 
			
		||||
import NextLink from 'next/link'
 | 
			
		||||
 | 
			
		||||
import { useSnapshot } from 'valtio'
 | 
			
		||||
import { useRouter } from 'next/router'
 | 
			
		||||
@@ -78,7 +78,7 @@ const Navigation = () => {
 | 
			
		||||
            pr: '$4'
 | 
			
		||||
          }}
 | 
			
		||||
        >
 | 
			
		||||
          <Link href={gistId ? `/develop/${gistId}` : '/develop'} passHref>
 | 
			
		||||
          <NextLink legacyBehavior href={gistId ? `/develop/${gistId}` : '/develop'} passHref>
 | 
			
		||||
            <Box
 | 
			
		||||
              as="a"
 | 
			
		||||
              css={{
 | 
			
		||||
@@ -89,7 +89,7 @@ const Navigation = () => {
 | 
			
		||||
            >
 | 
			
		||||
              <Logo width="32px" height="32px" />
 | 
			
		||||
            </Box>
 | 
			
		||||
          </Link>
 | 
			
		||||
          </NextLink>
 | 
			
		||||
          <Flex
 | 
			
		||||
            css={{
 | 
			
		||||
              ml: '$5',
 | 
			
		||||
@@ -105,7 +105,8 @@ const Navigation = () => {
 | 
			
		||||
                <Text css={{ fontSize: '$xs', color: '$mauve10', lineHeight: 1 }}>
 | 
			
		||||
                  {snap.files.length > 0 ? 'Gist: ' : 'Builder'}
 | 
			
		||||
                  {snap.files.length > 0 && (
 | 
			
		||||
                    <Link
 | 
			
		||||
                    <NextLink
 | 
			
		||||
                      legacyBehavior
 | 
			
		||||
                      href={`https://gist.github.com/${snap.gistOwner || ''}/${snap.gistId || ''}`}
 | 
			
		||||
                      passHref
 | 
			
		||||
                    >
 | 
			
		||||
@@ -117,7 +118,7 @@ const Navigation = () => {
 | 
			
		||||
                      >
 | 
			
		||||
                        {`${snap.gistOwner || '-'}/${truncate(snap.gistId || '')}`}
 | 
			
		||||
                      </Text>
 | 
			
		||||
                    </Link>
 | 
			
		||||
                    </NextLink>
 | 
			
		||||
                  )}
 | 
			
		||||
                </Text>
 | 
			
		||||
              </>
 | 
			
		||||
@@ -336,29 +337,39 @@ const Navigation = () => {
 | 
			
		||||
            }}
 | 
			
		||||
          >
 | 
			
		||||
            <ButtonGroup>
 | 
			
		||||
              <Link href={gistId ? `/develop/${gistId}` : '/develop'} passHref shallow>
 | 
			
		||||
              <NextLink
 | 
			
		||||
                legacyBehavior
 | 
			
		||||
                href={gistId ? `/develop/${gistId}` : '/develop'}
 | 
			
		||||
                passHref
 | 
			
		||||
                shallow
 | 
			
		||||
              >
 | 
			
		||||
                <Button as="a" outline={!router.pathname.includes('/develop')} uppercase>
 | 
			
		||||
                  Develop
 | 
			
		||||
                </Button>
 | 
			
		||||
              </Link>
 | 
			
		||||
              <Link href={gistId ? `/deploy/${gistId}` : '/deploy'} passHref shallow>
 | 
			
		||||
              </NextLink>
 | 
			
		||||
              <NextLink
 | 
			
		||||
                legacyBehavior
 | 
			
		||||
                href={gistId ? `/deploy/${gistId}` : '/deploy'}
 | 
			
		||||
                passHref
 | 
			
		||||
                shallow
 | 
			
		||||
              >
 | 
			
		||||
                <Button as="a" outline={!router.pathname.includes('/deploy')} uppercase>
 | 
			
		||||
                  Deploy
 | 
			
		||||
                </Button>
 | 
			
		||||
              </Link>
 | 
			
		||||
              <Link href={gistId ? `/test/${gistId}` : '/test'} passHref shallow>
 | 
			
		||||
              </NextLink>
 | 
			
		||||
              <NextLink legacyBehavior href={gistId ? `/test/${gistId}` : '/test'} passHref shallow>
 | 
			
		||||
                <Button as="a" outline={!router.pathname.includes('/test')} uppercase>
 | 
			
		||||
                  Test
 | 
			
		||||
                </Button>
 | 
			
		||||
              </Link>
 | 
			
		||||
              </NextLink>
 | 
			
		||||
            </ButtonGroup>
 | 
			
		||||
            <Link href="https://xrpl-hooks.readme.io/v2.0" passHref>
 | 
			
		||||
            <NextLink legacyBehavior href="https://xrpl-hooks.readme.io/v2.0" passHref>
 | 
			
		||||
              <a target="_blank" rel="noreferrer noopener">
 | 
			
		||||
                <Button outline>
 | 
			
		||||
                  <BookOpen size="15px" />
 | 
			
		||||
                </Button>
 | 
			
		||||
              </a>
 | 
			
		||||
            </Link>
 | 
			
		||||
            </NextLink>
 | 
			
		||||
          </Stack>
 | 
			
		||||
        </Flex>
 | 
			
		||||
      </Container>
 | 
			
		||||
 
 | 
			
		||||
@@ -3,13 +3,11 @@ import { mauve, mauveDark, purple, purpleDark } from '@radix-ui/colors'
 | 
			
		||||
import { useTheme } from 'next-themes'
 | 
			
		||||
import { styled } from '../stitches.config'
 | 
			
		||||
import dynamic from 'next/dynamic'
 | 
			
		||||
import type { Props } from 'react-select'
 | 
			
		||||
import type { Props, StylesConfig } from 'react-select'
 | 
			
		||||
const SelectInput = dynamic(() => import('react-select'), { ssr: false })
 | 
			
		||||
const CreatableSelectInput = dynamic(() => import('react-select/creatable'), { ssr: false })
 | 
			
		||||
 | 
			
		||||
// eslint-disable-next-line react/display-name
 | 
			
		||||
const Select = forwardRef<any, Props>((props, ref) => {
 | 
			
		||||
  const { theme } = useTheme()
 | 
			
		||||
  const isDark = theme === 'dark'
 | 
			
		||||
const getColors = (isDark: boolean) => {
 | 
			
		||||
  const colors: any = {
 | 
			
		||||
    // primary: pink.pink9,
 | 
			
		||||
    active: isDark ? purpleDark.purple9 : purple.purple9,
 | 
			
		||||
@@ -30,94 +28,136 @@ const Select = forwardRef<any, Props>((props, ref) => {
 | 
			
		||||
  }
 | 
			
		||||
  colors.outline = colors.background
 | 
			
		||||
  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 (
 | 
			
		||||
    <SelectInput
 | 
			
		||||
      ref={ref}
 | 
			
		||||
      menuPosition={props.menuPosition || 'fixed'}
 | 
			
		||||
      styles={{
 | 
			
		||||
        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,
 | 
			
		||||
            color: state.isFocused ? colors.border : colors.secondary,
 | 
			
		||||
            ':hover': {
 | 
			
		||||
              color: colors.border
 | 
			
		||||
            }
 | 
			
		||||
          }
 | 
			
		||||
        }
 | 
			
		||||
      }}
 | 
			
		||||
      styles={styles}
 | 
			
		||||
      {...props}
 | 
			
		||||
    />
 | 
			
		||||
  )
 | 
			
		||||
})
 | 
			
		||||
 | 
			
		||||
// eslint-disable-next-line react/display-name
 | 
			
		||||
const Creatable = forwardRef<any, Props>((props, ref) => {
 | 
			
		||||
  const { theme } = useTheme()
 | 
			
		||||
  const isDark = theme === 'dark'
 | 
			
		||||
  const styles = getStyles(isDark)
 | 
			
		||||
  return (
 | 
			
		||||
    <CreatableSelectInput
 | 
			
		||||
      ref={ref}
 | 
			
		||||
      formatCreateLabel={label => `Enter "${label}"`}
 | 
			
		||||
      menuPosition={props.menuPosition || 'fixed'}
 | 
			
		||||
      styles={styles}
 | 
			
		||||
      {...props}
 | 
			
		||||
    />
 | 
			
		||||
  )
 | 
			
		||||
})
 | 
			
		||||
 | 
			
		||||
export default styled(Select, {})
 | 
			
		||||
export const CreatableSelect = styled(Creatable, {})
 | 
			
		||||
 
 | 
			
		||||
@@ -43,7 +43,6 @@ const Transaction: FC<TransactionProps> = ({ header, state: txState, ...props })
 | 
			
		||||
    (state: Partial<TransactionState> = txState) => {
 | 
			
		||||
      const {
 | 
			
		||||
        selectedTransaction,
 | 
			
		||||
        selectedDestAccount,
 | 
			
		||||
        selectedAccount,
 | 
			
		||||
        txFields,
 | 
			
		||||
        selectedFlags,
 | 
			
		||||
@@ -52,20 +51,19 @@ const Transaction: FC<TransactionProps> = ({ header, state: txState, ...props })
 | 
			
		||||
      } = state
 | 
			
		||||
 | 
			
		||||
      const TransactionType = selectedTransaction?.value || null
 | 
			
		||||
      const Destination = selectedDestAccount?.value || txFields?.Destination
 | 
			
		||||
      const Account = selectedAccount?.value || null
 | 
			
		||||
      const Flags = combineFlags(selectedFlags?.map(flag => flag.value)) || txFields?.Flags
 | 
			
		||||
      const HookParameters = Object.entries(hookParameters || {}).reduce<
 | 
			
		||||
        SetHookData['HookParameters']
 | 
			
		||||
      >((acc, [_, { label, value }]) => {
 | 
			
		||||
        return acc.concat({
 | 
			
		||||
          HookParameter: { HookParameterName: toHex(label), HookParameterValue: toHex(value) }
 | 
			
		||||
          HookParameter: { HookParameterName: toHex(label), HookParameterValue: 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) }
 | 
			
		||||
              Memo: { MemoData: data, MemoFormat: toHex(format), MemoType: toHex(type) }
 | 
			
		||||
            })
 | 
			
		||||
          }, [])
 | 
			
		||||
        : undefined
 | 
			
		||||
@@ -75,7 +73,6 @@ const Transaction: FC<TransactionProps> = ({ header, state: txState, ...props })
 | 
			
		||||
        HookParameters,
 | 
			
		||||
        Flags,
 | 
			
		||||
        TransactionType,
 | 
			
		||||
        Destination,
 | 
			
		||||
        Account,
 | 
			
		||||
        Memos
 | 
			
		||||
      })
 | 
			
		||||
@@ -128,11 +125,12 @@ const Transaction: FC<TransactionProps> = ({ header, state: txState, ...props })
 | 
			
		||||
        throw Error('Account must be selected from imported accounts!')
 | 
			
		||||
      }
 | 
			
		||||
      const options = prepareOptions(st)
 | 
			
		||||
 | 
			
		||||
      const fields = getTxFields(options.TransactionType)
 | 
			
		||||
      if (fields.Destination && !options.Destination) {
 | 
			
		||||
        throw Error('Destination account is required!')
 | 
			
		||||
      }
 | 
			
		||||
      // delete unnecessary fields
 | 
			
		||||
      Object.keys(options).forEach(field => {
 | 
			
		||||
        if (!options[field]) {
 | 
			
		||||
          delete options[field]
 | 
			
		||||
        }
 | 
			
		||||
      })
 | 
			
		||||
 | 
			
		||||
      await sendTransaction(account, options, { logPrefix })
 | 
			
		||||
    } catch (error) {
 | 
			
		||||
@@ -167,13 +165,6 @@ const Transaction: FC<TransactionProps> = ({ header, state: txState, ...props })
 | 
			
		||||
        selectedTransaction: transactionType
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      if (fields.Destination !== undefined) {
 | 
			
		||||
        nwState.selectedDestAccount = null
 | 
			
		||||
        fields.Destination = ''
 | 
			
		||||
      } else {
 | 
			
		||||
        fields.Destination = undefined
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      if (transactionType?.value && transactionFlags[transactionType?.value] && fields.Flags) {
 | 
			
		||||
        nwState.selectedFlags = extractFlags(transactionType.value, fields.Flags)
 | 
			
		||||
        fields.Flags = undefined
 | 
			
		||||
 
 | 
			
		||||
@@ -1,25 +1,25 @@
 | 
			
		||||
import { FC, ReactNode, useCallback, useEffect, useMemo, useState } from 'react'
 | 
			
		||||
import { FC, ReactNode, useCallback, useEffect, useState } from 'react'
 | 
			
		||||
import Container from '../Container'
 | 
			
		||||
import Flex from '../Flex'
 | 
			
		||||
import Input from '../Input'
 | 
			
		||||
import Select from '../Select'
 | 
			
		||||
import Select, { CreatableSelect } from '../Select'
 | 
			
		||||
import Text from '../Text'
 | 
			
		||||
import {
 | 
			
		||||
  SelectOption,
 | 
			
		||||
  TransactionState,
 | 
			
		||||
  transactionsOptions,
 | 
			
		||||
  TxFields,
 | 
			
		||||
  getTxFields,
 | 
			
		||||
  defaultTransactionType
 | 
			
		||||
} from '../../state/transactions'
 | 
			
		||||
import { useSnapshot } from 'valtio'
 | 
			
		||||
import state from '../../state'
 | 
			
		||||
import { streamState } from '../DebugStream'
 | 
			
		||||
import { Button } from '..'
 | 
			
		||||
import { Box, Button } from '..'
 | 
			
		||||
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 {
 | 
			
		||||
  setState: (pTx?: Partial<TransactionState> | undefined) => TransactionState | undefined
 | 
			
		||||
@@ -37,28 +37,14 @@ export const TxUI: FC<UIProps> = ({
 | 
			
		||||
  switchToJson
 | 
			
		||||
}) => {
 | 
			
		||||
  const { accounts } = useSnapshot(state)
 | 
			
		||||
  const {
 | 
			
		||||
    selectedAccount,
 | 
			
		||||
    selectedDestAccount,
 | 
			
		||||
    selectedTransaction,
 | 
			
		||||
    txFields,
 | 
			
		||||
    selectedFlags,
 | 
			
		||||
    hookParameters,
 | 
			
		||||
    memos
 | 
			
		||||
  } = txState
 | 
			
		||||
  const { selectedAccount, selectedTransaction, txFields, selectedFlags, hookParameters, memos } =
 | 
			
		||||
    txState
 | 
			
		||||
 | 
			
		||||
  const accountOptions: SelectOption[] = accounts.map(acc => ({
 | 
			
		||||
    label: acc.name,
 | 
			
		||||
    value: acc.address
 | 
			
		||||
  }))
 | 
			
		||||
 | 
			
		||||
  const destAccountOptions: SelectOption[] = accounts
 | 
			
		||||
    .map(acc => ({
 | 
			
		||||
      label: acc.name,
 | 
			
		||||
      value: acc.address
 | 
			
		||||
    }))
 | 
			
		||||
    .filter(acc => acc.value !== selectedAccount?.value)
 | 
			
		||||
 | 
			
		||||
  const flagsOptions: SelectOption[] = Object.entries(
 | 
			
		||||
    getFlags(selectedTransaction?.value) || {}
 | 
			
		||||
  ).map(([label, value]) => ({
 | 
			
		||||
@@ -87,6 +73,22 @@ export const TxUI: FC<UIProps> = ({
 | 
			
		||||
    [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(
 | 
			
		||||
    async (state?: TransactionState, silent?: boolean) => {
 | 
			
		||||
      setFeeLoading(true)
 | 
			
		||||
@@ -119,21 +121,23 @@ export const TxUI: FC<UIProps> = ({
 | 
			
		||||
    }
 | 
			
		||||
  }, [handleChangeTxType, selectedTransaction?.value])
 | 
			
		||||
 | 
			
		||||
  const fields = useMemo(
 | 
			
		||||
    () => getTxFields(selectedTransaction?.value),
 | 
			
		||||
    [selectedTransaction?.value]
 | 
			
		||||
  )
 | 
			
		||||
 | 
			
		||||
  const richFields = ['TransactionType', 'Account', 'HookParameters', 'Memos']
 | 
			
		||||
  if (fields.Destination !== undefined) {
 | 
			
		||||
    richFields.push('Destination')
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  if (flagsOptions.length) {
 | 
			
		||||
    richFields.push('Flags')
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  const otherFields = Object.keys(txFields).filter(k => !richFields.includes(k)) as [keyof TxFields]
 | 
			
		||||
  const amountOptions = [
 | 
			
		||||
    { label: 'XRP', value: 'xrp' },
 | 
			
		||||
    { label: 'Token', value: 'token' }
 | 
			
		||||
  ] as const
 | 
			
		||||
 | 
			
		||||
  const defaultTokenAmount = {
 | 
			
		||||
    value: '0',
 | 
			
		||||
    currency: '',
 | 
			
		||||
    issuer: ''
 | 
			
		||||
  }
 | 
			
		||||
  return (
 | 
			
		||||
    <Container
 | 
			
		||||
      css={{
 | 
			
		||||
@@ -165,18 +169,6 @@ export const TxUI: FC<UIProps> = ({
 | 
			
		||||
        <TxField label="Sequence">
 | 
			
		||||
          <AccountSequence address={selectedAccount?.value} />
 | 
			
		||||
        </TxField>
 | 
			
		||||
        {richFields.includes('Destination') && (
 | 
			
		||||
          <TxField label="Destination account">
 | 
			
		||||
            <Select
 | 
			
		||||
              instanceId="to-account"
 | 
			
		||||
              placeholder="Select the destination account"
 | 
			
		||||
              options={destAccountOptions}
 | 
			
		||||
              value={selectedDestAccount}
 | 
			
		||||
              isClearable
 | 
			
		||||
              onChange={(acc: any) => setState({ selectedDestAccount: acc })}
 | 
			
		||||
            />
 | 
			
		||||
          </TxField>
 | 
			
		||||
        )}
 | 
			
		||||
        {richFields.includes('Flags') && (
 | 
			
		||||
          <TxField label="Flags">
 | 
			
		||||
            <Select
 | 
			
		||||
@@ -198,23 +190,133 @@ export const TxUI: FC<UIProps> = ({
 | 
			
		||||
          let _value = txFields[field]
 | 
			
		||||
 | 
			
		||||
          let value: string | undefined
 | 
			
		||||
          if (typeof _value === 'object') {
 | 
			
		||||
            if (_value.$type === 'json' && typeof _value.$value === 'object') {
 | 
			
		||||
          if (typeIs(_value, 'object')) {
 | 
			
		||||
            if (_value.$type === 'json' && typeIs(_value.$value, ['object', 'array'])) {
 | 
			
		||||
              value = JSON.stringify(_value.$value, null, 2)
 | 
			
		||||
            } else {
 | 
			
		||||
              value = _value.$value.toString()
 | 
			
		||||
              value = _value.$value?.toString()
 | 
			
		||||
            }
 | 
			
		||||
          } else {
 | 
			
		||||
            value = _value?.toString()
 | 
			
		||||
          }
 | 
			
		||||
 | 
			
		||||
          const isXrp = typeof _value === 'object' && _value.$type === 'xrp'
 | 
			
		||||
          const isAccount = typeIs(_value, 'object') && _value.$type === 'account'
 | 
			
		||||
          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 isFee = field === 'Fee'
 | 
			
		||||
          let rows = isJson ? (value?.match(/\n/gm)?.length || 0) + 1 : undefined
 | 
			
		||||
          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 (
 | 
			
		||||
            <TxField key={field} label={field + (isXrp ? ' (XRP)' : '')}>
 | 
			
		||||
            <TxField key={field} label={field}>
 | 
			
		||||
              {isJson ? (
 | 
			
		||||
                <Textarea
 | 
			
		||||
                  rows={rows}
 | 
			
		||||
@@ -304,7 +406,7 @@ export const TxUI: FC<UIProps> = ({
 | 
			
		||||
                  />
 | 
			
		||||
                  <Input
 | 
			
		||||
                    css={{ mx: '$2' }}
 | 
			
		||||
                    placeholder="Value"
 | 
			
		||||
                    placeholder="Value (hex-quoted)"
 | 
			
		||||
                    value={value}
 | 
			
		||||
                    onChange={e => {
 | 
			
		||||
                      setState({
 | 
			
		||||
@@ -367,7 +469,7 @@ export const TxUI: FC<UIProps> = ({
 | 
			
		||||
                    }}
 | 
			
		||||
                  />
 | 
			
		||||
                  <Input
 | 
			
		||||
                    placeholder="Data"
 | 
			
		||||
                    placeholder="Data (hex-quoted)"
 | 
			
		||||
                    css={{ mx: '$2' }}
 | 
			
		||||
                    value={memo.data}
 | 
			
		||||
                    onChange={e => {
 | 
			
		||||
@@ -425,6 +527,35 @@ export const TxUI: FC<UIProps> = ({
 | 
			
		||||
  )
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
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,
 | 
			
		||||
@@ -438,7 +569,7 @@ export const TxField: FC<{ label: string; children: ReactNode; multiLine?: boole
 | 
			
		||||
        justifyContent: 'flex-end',
 | 
			
		||||
        alignItems: multiLine ? 'flex-start' : 'center',
 | 
			
		||||
        position: 'relative',
 | 
			
		||||
        mb: '$3',
 | 
			
		||||
        mb: '$2',
 | 
			
		||||
        mt: '1px',
 | 
			
		||||
        pr: '1px'
 | 
			
		||||
      }}
 | 
			
		||||
 
 | 
			
		||||
@@ -2,7 +2,10 @@
 | 
			
		||||
  {
 | 
			
		||||
    "TransactionType": "AccountDelete",
 | 
			
		||||
    "Account": "rWYkbWkCeg8dP6rXALnjgZSjjLyih5NXm",
 | 
			
		||||
    "Destination": "rPT1Sjq2YGrBMTttX4GZHjKu9dyfzbpAYe",
 | 
			
		||||
    "Destination": {
 | 
			
		||||
      "$type": "account",
 | 
			
		||||
      "$value": ""
 | 
			
		||||
    },
 | 
			
		||||
    "DestinationTag": 13,
 | 
			
		||||
    "Fee": "2000000",
 | 
			
		||||
    "Sequence": 2470665,
 | 
			
		||||
@@ -28,7 +31,11 @@
 | 
			
		||||
    "TransactionType": "CheckCash",
 | 
			
		||||
    "Amount": {
 | 
			
		||||
      "$value": "100",
 | 
			
		||||
      "$type": "xrp"
 | 
			
		||||
      "$type": "amount.xrp"
 | 
			
		||||
    },
 | 
			
		||||
    "DeliverMin": {
 | 
			
		||||
      "$value": "",
 | 
			
		||||
      "$type": "amount.xrp"
 | 
			
		||||
    },
 | 
			
		||||
    "CheckID": "838766BA2B995C00744175F69A1B11E32C3DBC40E64801A4056FCBD657F57334",
 | 
			
		||||
    "Fee": "12"
 | 
			
		||||
@@ -36,7 +43,10 @@
 | 
			
		||||
  {
 | 
			
		||||
    "TransactionType": "CheckCreate",
 | 
			
		||||
    "Account": "rUn84CUYbNjRoTQ6mSW7BVJPSVJNLb1QLo",
 | 
			
		||||
    "Destination": "rfkE1aSy9G8Upk4JssnwBxhEv5p4mn2KTy",
 | 
			
		||||
    "Destination": {
 | 
			
		||||
      "$type": "account",
 | 
			
		||||
      "$value": ""
 | 
			
		||||
    },
 | 
			
		||||
    "SendMax": "100000000",
 | 
			
		||||
    "Expiration": 570113521,
 | 
			
		||||
    "InvoiceID": "6F1DFD1D0FE8A32E40E1F2C05CF1C15545BAB56B617F9C6C2D63A6B704BEF59B",
 | 
			
		||||
@@ -54,7 +64,10 @@
 | 
			
		||||
  {
 | 
			
		||||
    "Account": "rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn",
 | 
			
		||||
    "TransactionType": "EscrowCancel",
 | 
			
		||||
    "Owner": "rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn",
 | 
			
		||||
    "Owner": {
 | 
			
		||||
      "$type": "account",
 | 
			
		||||
      "$value": ""
 | 
			
		||||
    },
 | 
			
		||||
    "OfferSequence": 7,
 | 
			
		||||
    "Fee": "10"
 | 
			
		||||
  },
 | 
			
		||||
@@ -63,9 +76,12 @@
 | 
			
		||||
    "TransactionType": "EscrowCreate",
 | 
			
		||||
    "Amount": {
 | 
			
		||||
      "$value": "100",
 | 
			
		||||
      "$type": "xrp"
 | 
			
		||||
      "$type": "amount.xrp"
 | 
			
		||||
    },
 | 
			
		||||
    "Destination": {
 | 
			
		||||
      "$type": "account",
 | 
			
		||||
      "$value": ""
 | 
			
		||||
    },
 | 
			
		||||
    "Destination": "rsA2LpzuawewSBQXkiju3YQTMzW13pAAdW",
 | 
			
		||||
    "CancelAfter": 533257958,
 | 
			
		||||
    "FinishAfter": 533171558,
 | 
			
		||||
    "Condition": "A0258020E3B0C44298FC1C149AFBF4C8996FB92427AE41E4649B934CA495991B7852B855810100",
 | 
			
		||||
@@ -76,7 +92,10 @@
 | 
			
		||||
  {
 | 
			
		||||
    "Account": "rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn",
 | 
			
		||||
    "TransactionType": "EscrowFinish",
 | 
			
		||||
    "Owner": "rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn",
 | 
			
		||||
    "Owner": {
 | 
			
		||||
      "$type": "account",
 | 
			
		||||
      "$value": ""
 | 
			
		||||
    },
 | 
			
		||||
    "OfferSequence": 7,
 | 
			
		||||
    "Condition": "A0258020E3B0C44298FC1C149AFBF4C8996FB92427AE41E4649B934CA495991B7852B855810100",
 | 
			
		||||
    "Fulfillment": "A0028000",
 | 
			
		||||
@@ -120,10 +139,17 @@
 | 
			
		||||
    "NFTokenID": "000100001E962F495F07A990F4ED55ACCFEEF365DBAA76B6A048C0A200000007",
 | 
			
		||||
    "Amount": {
 | 
			
		||||
      "$value": "100",
 | 
			
		||||
      "$type": "xrp"
 | 
			
		||||
      "$type": "amount.xrp"
 | 
			
		||||
    },
 | 
			
		||||
    "Flags": "1",
 | 
			
		||||
    "Destination": "",
 | 
			
		||||
    "Destination": {
 | 
			
		||||
      "$type": "account",
 | 
			
		||||
      "$value": ""
 | 
			
		||||
    },
 | 
			
		||||
    "Owner": {
 | 
			
		||||
      "$type": "account",
 | 
			
		||||
      "$value": ""
 | 
			
		||||
    },
 | 
			
		||||
    "Fee": "10"
 | 
			
		||||
  },
 | 
			
		||||
  {
 | 
			
		||||
@@ -142,19 +168,29 @@
 | 
			
		||||
    "Flags": "0",
 | 
			
		||||
    "LastLedgerSequence": 7108682,
 | 
			
		||||
    "Sequence": 8,
 | 
			
		||||
    "TakerGets": "6000000",
 | 
			
		||||
    "Amount": {
 | 
			
		||||
      "$value": "100",
 | 
			
		||||
      "$type": "xrp"
 | 
			
		||||
    "TakerGets": {
 | 
			
		||||
      "$type": "amount.xrp",
 | 
			
		||||
      "$value": "6000000"
 | 
			
		||||
    },
 | 
			
		||||
    "TakerPays": {
 | 
			
		||||
      "$type": "amount.token",
 | 
			
		||||
      "$value": {
 | 
			
		||||
        "currency": "GKO",
 | 
			
		||||
        "issuer": "ruazs5h1qEsqpke88pcqnaseXdm6od2xc",
 | 
			
		||||
        "value": "2"
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
  },
 | 
			
		||||
  {
 | 
			
		||||
    "TransactionType": "Payment",
 | 
			
		||||
    "Account": "rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn",
 | 
			
		||||
    "Destination": "ra5nK24KXen9AHvsdFTKHSANinZseWnPcX",
 | 
			
		||||
    "Destination": {
 | 
			
		||||
      "$type": "account",
 | 
			
		||||
      "$value": ""
 | 
			
		||||
    },
 | 
			
		||||
    "Amount": {
 | 
			
		||||
      "$value": "100",
 | 
			
		||||
      "$type": "xrp"
 | 
			
		||||
      "$type": "amount.xrp"
 | 
			
		||||
    },
 | 
			
		||||
    "Fee": "12",
 | 
			
		||||
    "Flags": "2147483648",
 | 
			
		||||
@@ -165,9 +201,12 @@
 | 
			
		||||
    "TransactionType": "PaymentChannelCreate",
 | 
			
		||||
    "Amount": {
 | 
			
		||||
      "$value": "100",
 | 
			
		||||
      "$type": "xrp"
 | 
			
		||||
      "$type": "amount.xrp"
 | 
			
		||||
    },
 | 
			
		||||
    "Destination": {
 | 
			
		||||
      "$type": "account",
 | 
			
		||||
      "$value": ""
 | 
			
		||||
    },
 | 
			
		||||
    "Destination": "rsA2LpzuawewSBQXkiju3YQTMzW13pAAdW",
 | 
			
		||||
    "SettleDelay": 86400,
 | 
			
		||||
    "PublicKey": "32D2471DB72B27E3310F355BB33E339BF26F8392D5A93D3BC0FC3B566612DA0F0A",
 | 
			
		||||
    "CancelAfter": 533171558,
 | 
			
		||||
@@ -181,7 +220,7 @@
 | 
			
		||||
    "Channel": "C1AE6DDDEEC05CF2978C0BAD6FE302948E9533691DC749DCDD3B9E5992CA6198",
 | 
			
		||||
    "Amount": {
 | 
			
		||||
      "$value": "200",
 | 
			
		||||
      "$type": "xrp"
 | 
			
		||||
      "$type": "amount.xrp"
 | 
			
		||||
    },
 | 
			
		||||
    "Expiration": 543171558,
 | 
			
		||||
    "Fee": "10"
 | 
			
		||||
@@ -237,7 +276,7 @@
 | 
			
		||||
    "Flags": "262144",
 | 
			
		||||
    "LastLedgerSequence": 8007750,
 | 
			
		||||
    "LimitAmount": {
 | 
			
		||||
      "$type": "json",
 | 
			
		||||
      "$type": "amount.token",
 | 
			
		||||
      "$value": {
 | 
			
		||||
        "currency": "USD",
 | 
			
		||||
        "issuer": "rsP3mgGb2tcYUrxiLFiHJiQXhsziegtwBc",
 | 
			
		||||
@@ -248,6 +287,10 @@
 | 
			
		||||
  },
 | 
			
		||||
  {
 | 
			
		||||
    "TransactionType": "Invoke",
 | 
			
		||||
    "Destination": {
 | 
			
		||||
      "$type": "account",
 | 
			
		||||
      "$value": ""
 | 
			
		||||
    },
 | 
			
		||||
    "Fee": "12"
 | 
			
		||||
  },
 | 
			
		||||
  {
 | 
			
		||||
 
 | 
			
		||||
@@ -1,7 +1,7 @@
 | 
			
		||||
import type { NextRequest, NextFetchEvent } from 'next/server'
 | 
			
		||||
import type { NextRequest } from 'next/server'
 | 
			
		||||
import { NextResponse as Response } from 'next/server'
 | 
			
		||||
 | 
			
		||||
export default function middleware(req: NextRequest, ev: NextFetchEvent) {
 | 
			
		||||
export default function middleware(req: NextRequest) {
 | 
			
		||||
  if (req.nextUrl.pathname === '/') {
 | 
			
		||||
    const url = req.nextUrl.clone()
 | 
			
		||||
    url.pathname = '/develop'
 | 
			
		||||
@@ -8,8 +8,16 @@ module.exports = {
 | 
			
		||||
    config.resolve.alias['vscode'] = require.resolve(
 | 
			
		||||
      '@codingame/monaco-languageclient/lib/vscode-compatibility'
 | 
			
		||||
    )
 | 
			
		||||
    config.experiments = {
 | 
			
		||||
      topLevelAwait: true,
 | 
			
		||||
      layers: true
 | 
			
		||||
    }
 | 
			
		||||
    if (!isServer) {
 | 
			
		||||
      config.resolve.fallback.fs = false
 | 
			
		||||
      config.resolve.fallback = {
 | 
			
		||||
        ...config.resolve.fallback,
 | 
			
		||||
        fs: false,
 | 
			
		||||
        module: false
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
    config.module.rules.push({
 | 
			
		||||
      test: [/\.md$/, /hook-bundle\.js$/],
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										11
									
								
								package.json
									
									
									
									
									
								
							
							
						
						
									
										11
									
								
								package.json
									
									
									
									
									
								
							@@ -14,6 +14,7 @@
 | 
			
		||||
  "dependencies": {
 | 
			
		||||
    "@codingame/monaco-jsonrpc": "^0.3.1",
 | 
			
		||||
    "@codingame/monaco-languageclient": "^0.17.0",
 | 
			
		||||
    "@eqlabs/assemblyscript": "^0.0.0-alpha.1680097351",
 | 
			
		||||
    "@monaco-editor/react": "^4.4.5",
 | 
			
		||||
    "@octokit/core": "^3.5.1",
 | 
			
		||||
    "@radix-ui/colors": "^0.1.7",
 | 
			
		||||
@@ -37,7 +38,7 @@
 | 
			
		||||
    "lodash.uniqby": "^4.7.0",
 | 
			
		||||
    "lodash.xor": "^4.5.0",
 | 
			
		||||
    "monaco-editor": "^0.33.0",
 | 
			
		||||
    "next": "^12.0.4",
 | 
			
		||||
    "next": "^13.1.1",
 | 
			
		||||
    "next-auth": "^4.10.3",
 | 
			
		||||
    "next-plausible": "^3.2.0",
 | 
			
		||||
    "next-themes": "^0.1.1",
 | 
			
		||||
@@ -49,8 +50,8 @@
 | 
			
		||||
    "postinstall-postinstall": "^2.1.0",
 | 
			
		||||
    "prettier": "^2.7.1",
 | 
			
		||||
    "re-resizable": "^6.9.1",
 | 
			
		||||
    "react": "17.0.2",
 | 
			
		||||
    "react-dom": "17.0.2",
 | 
			
		||||
    "react": "^18.2.0",
 | 
			
		||||
    "react-dom": "^18.2.0",
 | 
			
		||||
    "react-hook-form": "^7.28.0",
 | 
			
		||||
    "react-hot-keys": "^2.7.1",
 | 
			
		||||
    "react-hot-toast": "^2.1.1",
 | 
			
		||||
@@ -65,7 +66,7 @@
 | 
			
		||||
    "valtio": "^1.2.5",
 | 
			
		||||
    "vscode-languageserver": "^7.0.0",
 | 
			
		||||
    "vscode-uri": "^3.0.2",
 | 
			
		||||
    "wabt": "^1.0.30",
 | 
			
		||||
    "wabt": "^1.0.32",
 | 
			
		||||
    "xrpl-accountlib": "^1.6.1",
 | 
			
		||||
    "xrpl-client": "^2.0.2"
 | 
			
		||||
  },
 | 
			
		||||
@@ -78,7 +79,7 @@
 | 
			
		||||
    "@types/react": "17.0.31",
 | 
			
		||||
    "browserify": "^17.0.0",
 | 
			
		||||
    "eslint": "7.32.0",
 | 
			
		||||
    "eslint-config-next": "11.1.2",
 | 
			
		||||
    "eslint-config-next": "^13.1.1",
 | 
			
		||||
    "raw-loader": "^4.0.2",
 | 
			
		||||
    "typescript": "4.4.4"
 | 
			
		||||
  },
 | 
			
		||||
 
 | 
			
		||||
@@ -24,7 +24,7 @@ import { ChatCircleText } from 'phosphor-react'
 | 
			
		||||
TimeAgo.setDefaultLocale(en.locale)
 | 
			
		||||
TimeAgo.addLocale(en)
 | 
			
		||||
 | 
			
		||||
function MyApp({ Component, pageProps: { session, ...pageProps } }: AppProps) {
 | 
			
		||||
function MyApp({ Component, pageProps: { session, ...pageProps } }: AppProps<{ session?: any }>) {
 | 
			
		||||
  const router = useRouter()
 | 
			
		||||
  const slug = router.query?.slug
 | 
			
		||||
  const gistId = (Array.isArray(slug) && slug[0]) ?? null
 | 
			
		||||
 
 | 
			
		||||
@@ -40,7 +40,7 @@ export default async function handler(
 | 
			
		||||
    }
 | 
			
		||||
    return res.status(200).json(json)
 | 
			
		||||
  } catch (err) {
 | 
			
		||||
    console.log(err)
 | 
			
		||||
    console.error(err)
 | 
			
		||||
    return res.status(500).json({ error: 'Server error' })
 | 
			
		||||
  }
 | 
			
		||||
  return res.status(500).json({ error: 'Not able to create faucet, try again' })
 | 
			
		||||
 
 | 
			
		||||
@@ -150,7 +150,9 @@ const Home: NextPage = () => {
 | 
			
		||||
 | 
			
		||||
  const activeFile = snap.files[snap.active] as IFile | undefined
 | 
			
		||||
  const activeFileExt = getFileExtention(activeFile?.name)
 | 
			
		||||
  const canCompile = activeFileExt === 'c' || activeFileExt === 'wat'
 | 
			
		||||
  const canCompile = activeFileExt === 'c' || activeFileExt === 'wat' || activeFileExt === 'ts'
 | 
			
		||||
 | 
			
		||||
  const isCompiling = snap.compiling.includes(snap.active);
 | 
			
		||||
  return (
 | 
			
		||||
    <Split
 | 
			
		||||
      direction="vertical"
 | 
			
		||||
@@ -166,7 +168,9 @@ const Home: NextPage = () => {
 | 
			
		||||
        {canCompile && (
 | 
			
		||||
          <Hotkeys
 | 
			
		||||
            keyName="command+b,ctrl+b"
 | 
			
		||||
            onKeyDown={() => !snap.compiling && snap.files.length && compileCode(snap.active)}
 | 
			
		||||
            onKeyDown={() =>
 | 
			
		||||
              snap.compiling === undefined && snap.files.length && compileCode(snap.active)
 | 
			
		||||
            }
 | 
			
		||||
          >
 | 
			
		||||
            <Flex
 | 
			
		||||
              css={{
 | 
			
		||||
@@ -183,7 +187,7 @@ const Home: NextPage = () => {
 | 
			
		||||
                variant="primary"
 | 
			
		||||
                uppercase
 | 
			
		||||
                disabled={!snap.files.length}
 | 
			
		||||
                isLoading={snap.compiling}
 | 
			
		||||
                isLoading={isCompiling}
 | 
			
		||||
                onClick={() => compileCode(snap.active)}
 | 
			
		||||
              >
 | 
			
		||||
                <Play weight="bold" size="16px" />
 | 
			
		||||
@@ -200,7 +204,9 @@ const Home: NextPage = () => {
 | 
			
		||||
        {activeFileExt === 'js' && (
 | 
			
		||||
          <Hotkeys
 | 
			
		||||
            keyName="command+b,ctrl+b"
 | 
			
		||||
            onKeyDown={() => !snap.compiling && snap.files.length && compileCode(snap.active)}
 | 
			
		||||
            onKeyDown={() =>
 | 
			
		||||
              !isCompiling && snap.files.length && compileCode(snap.active)
 | 
			
		||||
            }
 | 
			
		||||
          >
 | 
			
		||||
            <Flex
 | 
			
		||||
              css={{
 | 
			
		||||
 
 | 
			
		||||
@@ -1,97 +1,49 @@
 | 
			
		||||
import toast from 'react-hot-toast'
 | 
			
		||||
import Router from 'next/router'
 | 
			
		||||
 | 
			
		||||
import state from '../index'
 | 
			
		||||
import state, { IFile } from '../index'
 | 
			
		||||
import { saveFile } from './saveFile'
 | 
			
		||||
import { decodeBinary } from '../../utils/decodeBinary'
 | 
			
		||||
import { ref } from 'valtio'
 | 
			
		||||
import asc from "@eqlabs/assemblyscript/dist/asc"
 | 
			
		||||
import { getFileExtention } from '../../utils/helpers'
 | 
			
		||||
 | 
			
		||||
type CompilationResult = Pick<IFile, "compiledContent" | "compiledWatContent">
 | 
			
		||||
 | 
			
		||||
/* compileCode sends the code of the active file to compile endpoint
 | 
			
		||||
 * If all goes well you will get base64 encoded wasm file back with
 | 
			
		||||
 * some extra logging information if we can provide it. This function
 | 
			
		||||
 * also decodes the returned wasm and creates human readable WAT file
 | 
			
		||||
 * out of it and store both in global state.
 | 
			
		||||
 */
 | 
			
		||||
export const compileCode = async (activeId: number) => {
 | 
			
		||||
  // Save the file to global state
 | 
			
		||||
  saveFile(false, activeId)
 | 
			
		||||
  const file = state.files[activeId]
 | 
			
		||||
  if (file.name.endsWith('.wat')) {
 | 
			
		||||
    return compileWat(activeId)
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  if (!process.env.NEXT_PUBLIC_COMPILE_API_ENDPOINT) {
 | 
			
		||||
    throw Error('Missing env!')
 | 
			
		||||
  }
 | 
			
		||||
  // Bail out if we're already compiling
 | 
			
		||||
  if (state.compiling) {
 | 
			
		||||
    // if compiling is ongoing return // TODO Inform user about it.
 | 
			
		||||
  // Bail out if we're already compiling the file.
 | 
			
		||||
  if (!file || state.compiling.includes(activeId)) {
 | 
			
		||||
    return
 | 
			
		||||
  }
 | 
			
		||||
  // Set loading state to true
 | 
			
		||||
  state.compiling = true
 | 
			
		||||
  state.logs = []
 | 
			
		||||
 | 
			
		||||
  try {
 | 
			
		||||
    state.compiling.push(activeId)
 | 
			
		||||
    state.logs = []
 | 
			
		||||
    file.containsErrors = false
 | 
			
		||||
    let res: Response
 | 
			
		||||
    try {
 | 
			
		||||
      res = await fetch(process.env.NEXT_PUBLIC_COMPILE_API_ENDPOINT, {
 | 
			
		||||
        method: 'POST',
 | 
			
		||||
        headers: {
 | 
			
		||||
          'Content-Type': 'application/json'
 | 
			
		||||
        },
 | 
			
		||||
        body: JSON.stringify({
 | 
			
		||||
          output: 'wasm',
 | 
			
		||||
          compress: true,
 | 
			
		||||
          strip: state.compileOptions.strip,
 | 
			
		||||
          files: [
 | 
			
		||||
            {
 | 
			
		||||
              type: 'c',
 | 
			
		||||
              options: state.compileOptions.optimizationLevel || '-O2',
 | 
			
		||||
              name: file.name,
 | 
			
		||||
              src: file.content
 | 
			
		||||
            }
 | 
			
		||||
          ]
 | 
			
		||||
        })
 | 
			
		||||
      })
 | 
			
		||||
    } catch (error) {
 | 
			
		||||
      throw Error('Something went wrong, check your network connection and try again!')
 | 
			
		||||
 | 
			
		||||
    let result: CompilationResult;
 | 
			
		||||
    if (file.name.endsWith('.wat')) {
 | 
			
		||||
      result = await compileWat(file);
 | 
			
		||||
    }
 | 
			
		||||
    const json = await res.json()
 | 
			
		||||
    state.compiling = false
 | 
			
		||||
    if (!json.success) {
 | 
			
		||||
      const errors = [json.message]
 | 
			
		||||
      if (json.tasks && json.tasks.length > 0) {
 | 
			
		||||
        json.tasks.forEach((task: any) => {
 | 
			
		||||
          if (!task.success) {
 | 
			
		||||
            errors.push(task?.console)
 | 
			
		||||
          }
 | 
			
		||||
        })
 | 
			
		||||
      }
 | 
			
		||||
      throw errors
 | 
			
		||||
    else if (file.language === "ts") {
 | 
			
		||||
      result = await compileTs(file);
 | 
			
		||||
    }
 | 
			
		||||
    try {
 | 
			
		||||
      // Decode base64 encoded wasm that is coming back from the endpoint
 | 
			
		||||
      const bufferData = await decodeBinary(json.output)
 | 
			
		||||
 | 
			
		||||
      // Import wabt from and create human readable version of wasm file and
 | 
			
		||||
      // put it into state
 | 
			
		||||
      const ww = await (await import('wabt')).default()
 | 
			
		||||
      const myModule = ww.readWasm(new Uint8Array(bufferData), {
 | 
			
		||||
        readDebugNames: true
 | 
			
		||||
      })
 | 
			
		||||
      myModule.applyNames()
 | 
			
		||||
 | 
			
		||||
      const wast = myModule.toText({ foldExprs: false, inlineExport: false })
 | 
			
		||||
 | 
			
		||||
      file.compiledContent = ref(bufferData)
 | 
			
		||||
      file.lastCompiled = new Date()
 | 
			
		||||
      file.compiledValueSnapshot = file.content
 | 
			
		||||
      file.compiledWatContent = wast
 | 
			
		||||
    } catch (error) {
 | 
			
		||||
      throw Error('Invalid compilation result produced, check your code for errors and try again!')
 | 
			
		||||
    else if (navigator?.onLine === false) {
 | 
			
		||||
      throw Error('You seem offline, check you internet connection and try again!')
 | 
			
		||||
    }
 | 
			
		||||
    else if (file.language === 'c') {
 | 
			
		||||
      result = await compileC(file)
 | 
			
		||||
    }
 | 
			
		||||
    else throw Error("Unknown file type.")
 | 
			
		||||
 | 
			
		||||
    file.lastCompiled = new Date();
 | 
			
		||||
    file.compiledValueSnapshot = file.content;
 | 
			
		||||
    file.compiledContent = result.compiledContent;
 | 
			
		||||
    file.compiledWatContent = result.compiledWatContent;
 | 
			
		||||
    toast.success('Compiled successfully!', { position: 'bottom-center' })
 | 
			
		||||
    state.logs.push({
 | 
			
		||||
      type: 'success',
 | 
			
		||||
@@ -100,7 +52,8 @@ export const compileCode = async (activeId: number) => {
 | 
			
		||||
      linkText: 'Go to deploy'
 | 
			
		||||
    })
 | 
			
		||||
  } catch (err) {
 | 
			
		||||
    console.log(err)
 | 
			
		||||
    console.error(err)
 | 
			
		||||
    let message: string;
 | 
			
		||||
 | 
			
		||||
    if (err instanceof Array && typeof err[0] === 'string') {
 | 
			
		||||
      err.forEach(message => {
 | 
			
		||||
@@ -109,63 +62,157 @@ export const compileCode = async (activeId: number) => {
 | 
			
		||||
          message
 | 
			
		||||
        })
 | 
			
		||||
      })
 | 
			
		||||
      message = "Compilation errors occurred, see logs for more info."
 | 
			
		||||
    } else if (err instanceof Error) {
 | 
			
		||||
      state.logs.push({
 | 
			
		||||
        type: 'error',
 | 
			
		||||
        message: err.message
 | 
			
		||||
      })
 | 
			
		||||
      message = err.message
 | 
			
		||||
    } else {
 | 
			
		||||
      state.logs.push({
 | 
			
		||||
        type: 'error',
 | 
			
		||||
        message: 'Something went wrong, come back later!'
 | 
			
		||||
      })
 | 
			
		||||
      message = 'Something went wrong, try again later!'
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    state.compiling = false
 | 
			
		||||
    toast.error(`Error occurred while compiling!`, { position: 'bottom-center' })
 | 
			
		||||
    toast.error(message, { position: 'bottom-center' })
 | 
			
		||||
    file.containsErrors = true
 | 
			
		||||
  }
 | 
			
		||||
  state.compiling = state.compiling.filter(id => id !== activeId);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/* compileC sends the code of the active file to compile endpoint
 | 
			
		||||
 * If all goes well you will get base64 encoded wasm file back with
 | 
			
		||||
 * some extra logging information if we can provide it. This function
 | 
			
		||||
 * also decodes the returned wasm and creates human readable WAT file
 | 
			
		||||
 * out of it and store both in global state.
 | 
			
		||||
 */
 | 
			
		||||
export const compileC = async (file: IFile): Promise<CompilationResult> => {
 | 
			
		||||
  if (!process.env.NEXT_PUBLIC_COMPILE_API_ENDPOINT) {
 | 
			
		||||
    throw Error('Missing C compile endpoint!')
 | 
			
		||||
  }
 | 
			
		||||
  let res: Response
 | 
			
		||||
  res = await fetch(process.env.NEXT_PUBLIC_COMPILE_API_ENDPOINT, {
 | 
			
		||||
    method: 'POST',
 | 
			
		||||
    headers: {
 | 
			
		||||
      'Content-Type': 'application/json'
 | 
			
		||||
    },
 | 
			
		||||
    body: JSON.stringify({
 | 
			
		||||
      output: 'wasm',
 | 
			
		||||
      compress: true,
 | 
			
		||||
      strip: state.compileOptions.strip,
 | 
			
		||||
      files: [
 | 
			
		||||
        {
 | 
			
		||||
          type: 'c',
 | 
			
		||||
          options: state.compileOptions.optimizationLevel || '-O2',
 | 
			
		||||
          name: file.name,
 | 
			
		||||
          src: file.content
 | 
			
		||||
        }
 | 
			
		||||
      ]
 | 
			
		||||
    })
 | 
			
		||||
  })
 | 
			
		||||
  const json = await res.json()
 | 
			
		||||
 | 
			
		||||
  if (!json.success) {
 | 
			
		||||
    const errors = [json.message]
 | 
			
		||||
    if (json.tasks && json.tasks.length > 0) {
 | 
			
		||||
      json.tasks.forEach((task: any) => {
 | 
			
		||||
        if (!task.success) {
 | 
			
		||||
          errors.push(task?.console)
 | 
			
		||||
        }
 | 
			
		||||
      })
 | 
			
		||||
    }
 | 
			
		||||
    throw errors
 | 
			
		||||
  }
 | 
			
		||||
  try {
 | 
			
		||||
    // Decode base64 encoded wasm that is coming back from the endpoint
 | 
			
		||||
    const bufferData = await decodeBinary(json.output)
 | 
			
		||||
 | 
			
		||||
    // Import wabt from and create human readable version of wasm file and
 | 
			
		||||
    // put it into state
 | 
			
		||||
    const ww = await (await import('wabt')).default()
 | 
			
		||||
    const myModule = ww.readWasm(new Uint8Array(bufferData), {
 | 
			
		||||
      readDebugNames: true
 | 
			
		||||
    })
 | 
			
		||||
    myModule.applyNames()
 | 
			
		||||
 | 
			
		||||
    const wast = myModule.toText({ foldExprs: false, inlineExport: false })
 | 
			
		||||
 | 
			
		||||
    return {
 | 
			
		||||
      compiledContent: ref(bufferData),
 | 
			
		||||
      compiledWatContent: wast
 | 
			
		||||
    }
 | 
			
		||||
  } catch (error) {
 | 
			
		||||
    throw Error('Invalid compilation result produced, check your code for errors and try again!')
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export const compileWat = async (file: IFile): Promise<CompilationResult> => {
 | 
			
		||||
  const wabt = await (await import('wabt')).default()
 | 
			
		||||
  const mod = wabt.parseWat(file.name, file.content);
 | 
			
		||||
  mod.resolveNames();
 | 
			
		||||
  mod.validate();
 | 
			
		||||
  const { buffer } = mod.toBinary({
 | 
			
		||||
    log: false,
 | 
			
		||||
    write_debug_names: true,
 | 
			
		||||
  });
 | 
			
		||||
 | 
			
		||||
  return {
 | 
			
		||||
    compiledContent: ref(buffer),
 | 
			
		||||
    compiledWatContent: file.content,
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
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,
 | 
			
		||||
export const compileTs = async (file: IFile): Promise<CompilationResult> => {
 | 
			
		||||
  return new Promise(async (resolve, reject) => {
 | 
			
		||||
    let result: Partial<CompilationResult> = {}
 | 
			
		||||
    const { error, stdout, stderr } = await asc.main([
 | 
			
		||||
      // Command line options
 | 
			
		||||
      file.name,
 | 
			
		||||
      "--outFile", `${file.name}.wasm`,
 | 
			
		||||
      "--textFile", `${file.name}.wat`,
 | 
			
		||||
      "--runtime", "stub",
 | 
			
		||||
      "--initialMemory", "1",
 | 
			
		||||
      "--maximumMemory", "1",
 | 
			
		||||
      "--noExportMemory",
 | 
			
		||||
      "--optimize",
 | 
			
		||||
      "--topLevelToHook"
 | 
			
		||||
    ], {
 | 
			
		||||
      readFile: (name, baseDir) => {
 | 
			
		||||
        const file = state.files.find(file => file.name === name)
 | 
			
		||||
        if (file) {
 | 
			
		||||
          return file.content
 | 
			
		||||
        }
 | 
			
		||||
        return null
 | 
			
		||||
      },
 | 
			
		||||
      writeFile: async (name, data: ArrayBuffer | string, baseDir) => {
 | 
			
		||||
        const ext = getFileExtention(name);
 | 
			
		||||
        if (ext === 'wasm') {
 | 
			
		||||
          result.compiledContent = data as ArrayBuffer;
 | 
			
		||||
        }
 | 
			
		||||
        else if (ext === 'wat') {
 | 
			
		||||
          result.compiledWatContent = data as string;
 | 
			
		||||
        }
 | 
			
		||||
        if (result.compiledContent && result.compiledWatContent) {
 | 
			
		||||
          resolve({ ...result });
 | 
			
		||||
        }
 | 
			
		||||
      },
 | 
			
		||||
      listFiles: (dirname, baseDir) => {
 | 
			
		||||
        return state.files.map(file => file.name)
 | 
			
		||||
      },
 | 
			
		||||
      // reportDiagnostic?: ...,
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    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
 | 
			
		||||
    let logMsg = stdout.toString()
 | 
			
		||||
    let errMsg = stderr.toString()
 | 
			
		||||
    if (logMsg) {
 | 
			
		||||
      state.logs.push({
 | 
			
		||||
        type: "log",
 | 
			
		||||
        message: logMsg
 | 
			
		||||
      })
 | 
			
		||||
    }
 | 
			
		||||
    state.logs.push({
 | 
			
		||||
      type: 'error',
 | 
			
		||||
      message
 | 
			
		||||
    })
 | 
			
		||||
    toast.error(`Error occurred while compiling!`, { position: 'bottom-center' })
 | 
			
		||||
    file.containsErrors = true
 | 
			
		||||
  }
 | 
			
		||||
  state.compiling = false
 | 
			
		||||
    if (errMsg) {
 | 
			
		||||
      state.logs.push({
 | 
			
		||||
        type: "error",
 | 
			
		||||
        message: errMsg
 | 
			
		||||
      })
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    if (error) return reject(error)
 | 
			
		||||
  })
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -1,8 +1,8 @@
 | 
			
		||||
import { getFileExtention } from '../../utils/helpers'
 | 
			
		||||
import state, { IFile } from '../index'
 | 
			
		||||
import state, { IFile, ILang } from '../index'
 | 
			
		||||
 | 
			
		||||
const languageMapping: Record<string, string | undefined> = {
 | 
			
		||||
  ts: 'typescript',
 | 
			
		||||
const languageMapping: Record<string, ILang | undefined> = {
 | 
			
		||||
  ts: 'ts',
 | 
			
		||||
  js: 'javascript',
 | 
			
		||||
  md: 'markdown',
 | 
			
		||||
  c: 'c',
 | 
			
		||||
 
 | 
			
		||||
@@ -14,11 +14,4 @@ export const deleteAccount = (addr?: string) => {
 | 
			
		||||
      if (!acc) return
 | 
			
		||||
      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
 | 
			
		||||
    })
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -247,7 +247,7 @@ export const deleteHook = async (account: IAccount & { name?: string }) => {
 | 
			
		||||
      })
 | 
			
		||||
    }
 | 
			
		||||
  } catch (err) {
 | 
			
		||||
    console.log(err)
 | 
			
		||||
    console.error(err)
 | 
			
		||||
    toast.error('Error occurred while deleting hook', { id: toastId })
 | 
			
		||||
    state.deployLogs.push({
 | 
			
		||||
      type: 'error',
 | 
			
		||||
 
 | 
			
		||||
@@ -1,5 +1,5 @@
 | 
			
		||||
import { Octokit } from '@octokit/core'
 | 
			
		||||
import state, { IFile } from '../index'
 | 
			
		||||
import state, { IFile, ILang } from '../index'
 | 
			
		||||
import { templateFileIds } from '../constants'
 | 
			
		||||
 | 
			
		||||
const octokit = new Octokit()
 | 
			
		||||
@@ -48,7 +48,7 @@ export const fetchFiles = async (gistId: string) => {
 | 
			
		||||
 | 
			
		||||
    const files: IFile[] = Object.keys(res.data.files).map(filename => ({
 | 
			
		||||
      name: res.data.files?.[filename]?.filename || 'untitled.c',
 | 
			
		||||
      language: res.data.files?.[filename]?.language?.toLowerCase() || '',
 | 
			
		||||
      language: res.data.files?.[filename]?.language?.toLowerCase() as ILang | undefined,
 | 
			
		||||
      content: res.data.files?.[filename]?.content || ''
 | 
			
		||||
    }))
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -10,7 +10,6 @@ interface TransactionOptions {
 | 
			
		||||
  TransactionType: string
 | 
			
		||||
  Account?: string
 | 
			
		||||
  Fee?: string
 | 
			
		||||
  Destination?: string
 | 
			
		||||
  [index: string]: any
 | 
			
		||||
}
 | 
			
		||||
interface OtherOptions {
 | 
			
		||||
 
 | 
			
		||||
@@ -55,7 +55,7 @@ export const syncToGist = async (session?: Session | null, createNewGist?: boole
 | 
			
		||||
        return toast.success('Updated to gist successfully!', { id: toastId })
 | 
			
		||||
      })
 | 
			
		||||
      .catch(err => {
 | 
			
		||||
        console.log(err)
 | 
			
		||||
        console.error(err)
 | 
			
		||||
        state.gistLoading = false
 | 
			
		||||
        return toast.error(`Could not update Gist, try again later!`, {
 | 
			
		||||
          id: toastId
 | 
			
		||||
@@ -85,7 +85,7 @@ export const syncToGist = async (session?: Session | null, createNewGist?: boole
 | 
			
		||||
        return toast.success('Created new gist successfully!', { id: toastId })
 | 
			
		||||
      })
 | 
			
		||||
      .catch(err => {
 | 
			
		||||
        console.log(err)
 | 
			
		||||
        console.error(err)
 | 
			
		||||
        state.gistLoading = false
 | 
			
		||||
        return toast.error(`Could not create Gist, try again later!`, {
 | 
			
		||||
          id: toastId
 | 
			
		||||
 
 | 
			
		||||
@@ -9,9 +9,10 @@ declare module 'valtio' {
 | 
			
		||||
  function snapshot<T extends object>(p: T): T
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export type ILang = "ts" | "javascript" | "markdown" | "c" | "text"
 | 
			
		||||
export interface IFile {
 | 
			
		||||
  name: string
 | 
			
		||||
  language: string
 | 
			
		||||
  language: ILang | undefined
 | 
			
		||||
  content: string
 | 
			
		||||
  compiledValueSnapshot?: string
 | 
			
		||||
  compiledContent?: ArrayBuffer | null
 | 
			
		||||
@@ -66,7 +67,7 @@ export interface IState {
 | 
			
		||||
  loading: boolean
 | 
			
		||||
  gistLoading: boolean
 | 
			
		||||
  zipLoading: boolean
 | 
			
		||||
  compiling: boolean
 | 
			
		||||
  compiling: /* file id */ number[]
 | 
			
		||||
  logs: ILog[]
 | 
			
		||||
  deployLogs: ILog[]
 | 
			
		||||
  transactionLogs: ILog[]
 | 
			
		||||
@@ -98,7 +99,7 @@ let initialState: IState = {
 | 
			
		||||
  // Active file index on the Deploy page editor
 | 
			
		||||
  activeWat: 0,
 | 
			
		||||
  loading: false,
 | 
			
		||||
  compiling: false,
 | 
			
		||||
  compiling: [],
 | 
			
		||||
  logs: [],
 | 
			
		||||
  deployLogs: [],
 | 
			
		||||
  transactionLogs: [],
 | 
			
		||||
@@ -135,7 +136,7 @@ if (typeof window !== 'undefined') {
 | 
			
		||||
  try {
 | 
			
		||||
    localStorageAccounts = localStorage.getItem('hooksIdeAccounts')
 | 
			
		||||
  } catch (err) {
 | 
			
		||||
    console.log(`localStorage state broken`)
 | 
			
		||||
    console.error(`localStorage state broken`)
 | 
			
		||||
    localStorage.removeItem('hooksIdeAccounts')
 | 
			
		||||
  }
 | 
			
		||||
  if (localStorageAccounts) {
 | 
			
		||||
 
 | 
			
		||||
@@ -6,6 +6,7 @@ import { showAlert } from '../state/actions/showAlert'
 | 
			
		||||
import { parseJSON } from '../utils/json'
 | 
			
		||||
import { extractFlags, getFlags } from './constants/flags'
 | 
			
		||||
import { fromHex } from '../utils/setHook'
 | 
			
		||||
import { typeIs } from '../utils/helpers'
 | 
			
		||||
 | 
			
		||||
export type SelectOption = {
 | 
			
		||||
  value: string
 | 
			
		||||
@@ -27,7 +28,6 @@ export type Memos = {
 | 
			
		||||
export interface TransactionState {
 | 
			
		||||
  selectedTransaction: SelectOption | null
 | 
			
		||||
  selectedAccount: SelectOption | null
 | 
			
		||||
  selectedDestAccount: SelectOption | null
 | 
			
		||||
  selectedFlags: SelectOption[] | null
 | 
			
		||||
  hookParameters: HookParameters
 | 
			
		||||
  memos: Memos
 | 
			
		||||
@@ -50,7 +50,6 @@ export type TxFields = Omit<
 | 
			
		||||
export const defaultTransaction: TransactionState = {
 | 
			
		||||
  selectedTransaction: null,
 | 
			
		||||
  selectedAccount: null,
 | 
			
		||||
  selectedDestAccount: null,
 | 
			
		||||
  selectedFlags: null,
 | 
			
		||||
  hookParameters: {},
 | 
			
		||||
  memos: {},
 | 
			
		||||
@@ -130,46 +129,51 @@ export const modifyTxState = (
 | 
			
		||||
  return tx.state
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// state to tx options
 | 
			
		||||
export const prepareTransaction = (data: any) => {
 | 
			
		||||
  let options = { ...data }
 | 
			
		||||
 | 
			
		||||
  Object.keys(options).forEach(field => {
 | 
			
		||||
    let _value = options[field]
 | 
			
		||||
    // convert xrp
 | 
			
		||||
    if (_value && typeof _value === 'object' && _value.$type === 'xrp') {
 | 
			
		||||
      if (+_value.$value) {
 | 
			
		||||
        options[field] = (+_value.$value * 1000000 + '') as any
 | 
			
		||||
    if (!typeIs(_value, 'object')) return
 | 
			
		||||
    // amount.xrp
 | 
			
		||||
    if (_value.$type === 'amount.xrp') {
 | 
			
		||||
      if (_value.$value) {
 | 
			
		||||
        options[field] = (+(_value as any).$value * 1000000 + '')
 | 
			
		||||
      } else {
 | 
			
		||||
        options[field] = undefined // 👇 💀
 | 
			
		||||
        options[field] = ""
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
    // handle type: `json`
 | 
			
		||||
    if (_value && typeof _value === 'object' && _value.$type === 'json') {
 | 
			
		||||
      if (typeof _value.$value === 'object') {
 | 
			
		||||
    // amount.token
 | 
			
		||||
    if (_value.$type === 'amount.token') {
 | 
			
		||||
      if (typeIs(_value.$value, 'string')) {
 | 
			
		||||
        options[field] = parseJSON(_value.$value)
 | 
			
		||||
      } else if (typeIs(_value.$value, 'object')) {
 | 
			
		||||
        options[field] = _value.$value
 | 
			
		||||
      } else {
 | 
			
		||||
        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
 | 
			
		||||
        }
 | 
			
		||||
        options[field] = undefined
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // delete unnecessary fields
 | 
			
		||||
    if (!options[field]) {
 | 
			
		||||
      delete options[field]
 | 
			
		||||
    // account
 | 
			
		||||
    if (_value.$type === 'account') {
 | 
			
		||||
      options[field] = (_value.$value as any)?.toString() || ""
 | 
			
		||||
    }
 | 
			
		||||
    // json
 | 
			
		||||
    if (_value.$type === 'json') {
 | 
			
		||||
      const val = _value.$value;
 | 
			
		||||
      let res: any = val;
 | 
			
		||||
      if (typeIs(val, ["object", "array"])) {
 | 
			
		||||
        options[field] = res
 | 
			
		||||
      } else if (typeIs(val, "string") && (res = parseJSON(val))) {
 | 
			
		||||
        options[field] = res;
 | 
			
		||||
      } else {
 | 
			
		||||
        options[field] = res;
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
  })
 | 
			
		||||
 | 
			
		||||
  return options
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// editor value to state
 | 
			
		||||
export const prepareState = (value: string, transactionType?: string) => {
 | 
			
		||||
  const options = parseJSON(value)
 | 
			
		||||
  if (!options) {
 | 
			
		||||
@@ -179,7 +183,7 @@ export const prepareState = (value: string, transactionType?: string) => {
 | 
			
		||||
    return
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  const { Account, TransactionType, Destination, HookParameters, Memos, ...rest } = options
 | 
			
		||||
  const { Account, TransactionType, HookParameters, Memos, ...rest } = options
 | 
			
		||||
  let tx: Partial<TransactionState> = {}
 | 
			
		||||
  const schema = getTxFields(transactionType)
 | 
			
		||||
 | 
			
		||||
@@ -211,7 +215,7 @@ export const prepareState = (value: string, transactionType?: string) => {
 | 
			
		||||
 | 
			
		||||
  if (HookParameters && HookParameters instanceof Array) {
 | 
			
		||||
    tx.hookParameters = HookParameters.reduce<TransactionState["hookParameters"]>((acc, cur, idx) => {
 | 
			
		||||
      const param = { label: fromHex(cur.HookParameter?.HookParameterName || ""), value: fromHex(cur.HookParameter?.HookParameterValue || "") }
 | 
			
		||||
      const param = { label: fromHex(cur.HookParameter?.HookParameterName || ""), value: cur.HookParameter?.HookParameterValue || "" }
 | 
			
		||||
      acc[idx] = param;
 | 
			
		||||
      return acc;
 | 
			
		||||
    }, {})
 | 
			
		||||
@@ -219,30 +223,12 @@ export const prepareState = (value: string, transactionType?: string) => {
 | 
			
		||||
 | 
			
		||||
  if (Memos && Memos instanceof Array) {
 | 
			
		||||
    tx.memos = Memos.reduce<TransactionState["memos"]>((acc, cur, idx) => {
 | 
			
		||||
      const memo = { data: fromHex(cur.Memo?.MemoData || ""), type: fromHex(cur.Memo?.MemoType || ""), format: fromHex(cur.Memo?.MemoFormat || "") }
 | 
			
		||||
      const memo = { data: cur.Memo?.MemoData || "", type: fromHex(cur.Memo?.MemoType || ""), format: fromHex(cur.Memo?.MemoFormat || "") }
 | 
			
		||||
      acc[idx] = memo;
 | 
			
		||||
      return acc;
 | 
			
		||||
    }, {})
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  if (schema.Destination !== undefined) {
 | 
			
		||||
    const dest = state.accounts.find(acc => acc.address === Destination)
 | 
			
		||||
    if (dest) {
 | 
			
		||||
      tx.selectedDestAccount = {
 | 
			
		||||
        label: dest.name,
 | 
			
		||||
        value: dest.address
 | 
			
		||||
      }
 | 
			
		||||
    } else if (Destination) {
 | 
			
		||||
      tx.selectedDestAccount = {
 | 
			
		||||
        label: Destination,
 | 
			
		||||
        value: Destination
 | 
			
		||||
      }
 | 
			
		||||
    } else {
 | 
			
		||||
      tx.selectedDestAccount = null
 | 
			
		||||
    }
 | 
			
		||||
  } else if (Destination) {
 | 
			
		||||
    rest.Destination = Destination
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  if (getFlags(TransactionType) && rest.Flags) {
 | 
			
		||||
    const flags = extractFlags(TransactionType, rest.Flags)
 | 
			
		||||
@@ -254,17 +240,31 @@ export const prepareState = (value: string, transactionType?: string) => {
 | 
			
		||||
  Object.keys(rest).forEach(field => {
 | 
			
		||||
    const value = rest[field]
 | 
			
		||||
    const schemaVal = schema[field as keyof TxFields]
 | 
			
		||||
    const isXrp =
 | 
			
		||||
      typeof value !== 'object' &&
 | 
			
		||||
      schemaVal &&
 | 
			
		||||
      typeof schemaVal === 'object' &&
 | 
			
		||||
      schemaVal.$type === 'xrp'
 | 
			
		||||
    if (isXrp) {
 | 
			
		||||
 | 
			
		||||
    const isAmount = schemaVal &&
 | 
			
		||||
      typeIs(schemaVal, "object") &&
 | 
			
		||||
      schemaVal.$type.startsWith('amount.');
 | 
			
		||||
    const isAccount = schemaVal &&
 | 
			
		||||
      typeIs(schemaVal, "object") &&
 | 
			
		||||
      schemaVal.$type.startsWith("account");
 | 
			
		||||
 | 
			
		||||
    if (isAmount && ["number", "string"].includes(typeof value)) {
 | 
			
		||||
      rest[field] = {
 | 
			
		||||
        $type: 'xrp',
 | 
			
		||||
        $type: 'amount.xrp', // TODO narrow typed $type.
 | 
			
		||||
        $value: +value / 1000000 // ! maybe use bigint?
 | 
			
		||||
      }
 | 
			
		||||
    } else if (typeof value === 'object') {
 | 
			
		||||
    } else if (isAmount && typeof value === 'object') {
 | 
			
		||||
      rest[field] = {
 | 
			
		||||
        $type: 'amount.token',
 | 
			
		||||
        $value: value
 | 
			
		||||
      }
 | 
			
		||||
    } else if (isAccount) {
 | 
			
		||||
      rest[field] = {
 | 
			
		||||
        $type: "account",
 | 
			
		||||
        $value: value?.toString() || ""
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
    else if (typeof value === 'object') {
 | 
			
		||||
      rest[field] = {
 | 
			
		||||
        $type: 'json',
 | 
			
		||||
        $value: value
 | 
			
		||||
 
 | 
			
		||||
@@ -24,6 +24,9 @@ const estimateFee = async (
 | 
			
		||||
    const { signedTransaction } = sign(copyTx, keypair)
 | 
			
		||||
 | 
			
		||||
    const res = await xrplSend({ command: 'fee', tx_blob: signedTransaction })
 | 
			
		||||
    if (res.error) {
 | 
			
		||||
      throw new Error(`[${res.error}] ${res.error_exception}.`);
 | 
			
		||||
    }
 | 
			
		||||
    if (res && res.drops) {
 | 
			
		||||
      return res.drops
 | 
			
		||||
    }
 | 
			
		||||
@@ -31,7 +34,8 @@ const estimateFee = async (
 | 
			
		||||
  } catch (err) {
 | 
			
		||||
    if (!opts.silent) {
 | 
			
		||||
      console.error(err)
 | 
			
		||||
      toast.error('Cannot estimate fee.') // ? Some better msg
 | 
			
		||||
      const msg = err instanceof Error ? err.message : 'Error estimating fee!';
 | 
			
		||||
      toast.error(msg);
 | 
			
		||||
    }
 | 
			
		||||
    return null
 | 
			
		||||
  }
 | 
			
		||||
 
 | 
			
		||||
@@ -19,3 +19,23 @@ export const getFileExtention = (filename?: string): string | undefined => {
 | 
			
		||||
  const ext = (filename.includes('.') && filename.split('.').pop()) || undefined
 | 
			
		||||
  return ext
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export const getFileNamePart = (filename?: string): string | undefined => {
 | 
			
		||||
  if (!filename) return
 | 
			
		||||
  const name = (filename.includes('.') && filename.split('.').slice(0, -1).join(".")) || filename
 | 
			
		||||
  return name
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
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;
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -30,7 +30,7 @@ export const tts = {
 | 
			
		||||
export type TTS = typeof tts
 | 
			
		||||
 | 
			
		||||
const calculateHookOn = (arr: (keyof TTS)[]) => {
 | 
			
		||||
  let s = '0x3e3ff5bf'
 | 
			
		||||
  let s = '0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffbfffff'
 | 
			
		||||
  arr.forEach(n => {
 | 
			
		||||
    let v = BigInt(s)
 | 
			
		||||
    v ^= BigInt(1) << BigInt(tts[n])
 | 
			
		||||
 
 | 
			
		||||
@@ -1,9 +1,8 @@
 | 
			
		||||
import { typeIs, typeOf } from './helpers'
 | 
			
		||||
 | 
			
		||||
export const extractSchemaProps = <O extends object>(obj: O) =>
 | 
			
		||||
  Object.entries(obj).reduce((prev, [key, 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 value = typeIs(val, "object") && '$type' in val && '$value' in val ? val?.$value : val
 | 
			
		||||
    const type = typeOf(value)
 | 
			
		||||
 | 
			
		||||
    let schema: any = {
 | 
			
		||||
@@ -12,19 +11,19 @@ export const extractSchemaProps = <O extends object>(obj: O) =>
 | 
			
		||||
      default: value
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    if (typeOf(value) === 'array') {
 | 
			
		||||
    if (typeIs(value, "array")) {
 | 
			
		||||
      const item = value[0] // TODO merge other item schema's into one
 | 
			
		||||
      if (typeOf(item) !== 'object') {
 | 
			
		||||
      if (typeIs(item, "object")) {
 | 
			
		||||
        schema.items = {
 | 
			
		||||
          type: 'object',
 | 
			
		||||
          properties: extractSchemaProps(item),
 | 
			
		||||
          default: item
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
      // TODO support primitive-value arrays
 | 
			
		||||
      // TODO primitive-value arrays
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    if (typeOf(value) === 'object') {
 | 
			
		||||
    if (typeIs(value, "object")) {
 | 
			
		||||
      schema.properties = extractSchemaProps(value)
 | 
			
		||||
    }
 | 
			
		||||
    return {
 | 
			
		||||
 
 | 
			
		||||
@@ -85,7 +85,8 @@ export const getInvokeOptions = (content?: string) => {
 | 
			
		||||
export function toHex(str: string) {
 | 
			
		||||
  var result = ''
 | 
			
		||||
  for (var i = 0; i < str.length; i++) {
 | 
			
		||||
    result += str.charCodeAt(i).toString(16)
 | 
			
		||||
    const hex = str.charCodeAt(i).toString(16)
 | 
			
		||||
    result += hex.padStart(2, '0')
 | 
			
		||||
  }
 | 
			
		||||
  return result.toUpperCase()
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user