Compare commits

..

1 Commits

Author SHA1 Message Date
muzam1l
3340857575 Add owner field to NFTokenCreateOffer UI. 2023-03-14 21:38:35 +05:30
32 changed files with 865 additions and 1653 deletions

1
.gitignore vendored
View File

@@ -23,7 +23,6 @@
npm-debug.log* npm-debug.log*
yarn-debug.log* yarn-debug.log*
yarn-error.log* yarn-error.log*
.yarn
# local env files # local env files
.env.local .env.local

View File

@@ -94,7 +94,7 @@ const DeployEditor = () => {
}} }}
> >
{`You haven't compiled any files yet, compile files on `} {`You haven't compiled any files yet, compile files on `}
<NextLink legacyBehavior shallow href={`/develop/${router.query.slug}`} passHref> <NextLink shallow href={`/develop/${router.query.slug}`} passHref>
<Link as="a">develop view</Link> <Link as="a">develop view</Link>
</NextLink> </NextLink>
</Text> </Text>

View File

@@ -155,11 +155,9 @@ const EditorNavigation = ({ renderNav }: { renderNav?: () => ReactNode }) => {
> >
<Image <Image
src={session?.user?.image || ''} src={session?.user?.image || ''}
width="30" width="30px"
height="30" height="30px"
style={{ objectFit="cover"
objectFit: 'cover'
}}
alt="User avatar" alt="User avatar"
/> />
</Box> </Box>

View File

@@ -40,6 +40,7 @@ const EnrichLog: FC<EnrichLogProps> = ({ str }) => {
if (match.startsWith('HookSet')) { if (match.startsWith('HookSet')) {
const code = match.match(/^HookSet\((\d+)\)/)?.[1] const code = match.match(/^HookSet\((\d+)\)/)?.[1]
const val = hookSetCodes.find(v => code && v.code === +code) const val = hookSetCodes.find(v => code && v.code === +code)
console.log({ code, val })
if (!val) return match if (!val) return match
const content = capitalize(val.description) || 'No hint available!' const content = capitalize(val.description) || 'No hint available!'

View File

@@ -94,14 +94,6 @@ 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 HooksEditor = () => {
const editorRef = useRef<monaco.editor.IStandaloneCodeEditor>() const editorRef = useRef<monaco.editor.IStandaloneCodeEditor>()
const monacoRef = useRef<typeof monaco>() const monacoRef = useRef<typeof monaco>()
@@ -133,14 +125,6 @@ const HooksEditor = () => {
const file = snap.files[snap.active] 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 = () => ( const renderNav = () => (
<Tabs <Tabs
label="File" label="File"
@@ -237,7 +221,7 @@ const HooksEditor = () => {
monaco.languages.register({ monaco.languages.register({
id: 'text', id: 'text',
extensions: ['.txt'], extensions: ['.txt'],
mimetypes: ['text/plain'] mimetypes: ['text/plain'],
}) })
MonacoServices.install(monaco) MonacoServices.install(monaco)
const webSocket = createWebSocket( const webSocket = createWebSocket(
@@ -256,7 +240,7 @@ const HooksEditor = () => {
try { try {
disposable.dispose() disposable.dispose()
} catch (err) { } catch (err) {
console.error('err', err) console.log('err', err)
} }
}) })
} }

View File

@@ -160,7 +160,7 @@ export const Log: FC<ILog> = ({
)} )}
<Pre>{message}</Pre> <Pre>{message}</Pre>
{link && ( {link && (
<NextLink legacyBehavior href={link} shallow passHref> <NextLink href={link} shallow passHref>
<Link as="a">{linkText}</Link> <Link as="a">{linkText}</Link>
</NextLink> </NextLink>
)} )}

View File

@@ -1,5 +1,5 @@
import React from 'react' import React from 'react'
import NextLink from 'next/link' import Link from 'next/link'
import { useSnapshot } from 'valtio' import { useSnapshot } from 'valtio'
import { useRouter } from 'next/router' import { useRouter } from 'next/router'
@@ -78,7 +78,7 @@ const Navigation = () => {
pr: '$4' pr: '$4'
}} }}
> >
<NextLink legacyBehavior href={gistId ? `/develop/${gistId}` : '/develop'} passHref> <Link href={gistId ? `/develop/${gistId}` : '/develop'} passHref>
<Box <Box
as="a" as="a"
css={{ css={{
@@ -89,7 +89,7 @@ const Navigation = () => {
> >
<Logo width="32px" height="32px" /> <Logo width="32px" height="32px" />
</Box> </Box>
</NextLink> </Link>
<Flex <Flex
css={{ css={{
ml: '$5', ml: '$5',
@@ -105,8 +105,7 @@ const Navigation = () => {
<Text css={{ fontSize: '$xs', color: '$mauve10', lineHeight: 1 }}> <Text css={{ fontSize: '$xs', color: '$mauve10', lineHeight: 1 }}>
{snap.files.length > 0 ? 'Gist: ' : 'Builder'} {snap.files.length > 0 ? 'Gist: ' : 'Builder'}
{snap.files.length > 0 && ( {snap.files.length > 0 && (
<NextLink <Link
legacyBehavior
href={`https://gist.github.com/${snap.gistOwner || ''}/${snap.gistId || ''}`} href={`https://gist.github.com/${snap.gistOwner || ''}/${snap.gistId || ''}`}
passHref passHref
> >
@@ -118,7 +117,7 @@ const Navigation = () => {
> >
{`${snap.gistOwner || '-'}/${truncate(snap.gistId || '')}`} {`${snap.gistOwner || '-'}/${truncate(snap.gistId || '')}`}
</Text> </Text>
</NextLink> </Link>
)} )}
</Text> </Text>
</> </>
@@ -337,39 +336,29 @@ const Navigation = () => {
}} }}
> >
<ButtonGroup> <ButtonGroup>
<NextLink <Link href={gistId ? `/develop/${gistId}` : '/develop'} passHref shallow>
legacyBehavior
href={gistId ? `/develop/${gistId}` : '/develop'}
passHref
shallow
>
<Button as="a" outline={!router.pathname.includes('/develop')} uppercase> <Button as="a" outline={!router.pathname.includes('/develop')} uppercase>
Develop Develop
</Button> </Button>
</NextLink> </Link>
<NextLink <Link href={gistId ? `/deploy/${gistId}` : '/deploy'} passHref shallow>
legacyBehavior
href={gistId ? `/deploy/${gistId}` : '/deploy'}
passHref
shallow
>
<Button as="a" outline={!router.pathname.includes('/deploy')} uppercase> <Button as="a" outline={!router.pathname.includes('/deploy')} uppercase>
Deploy Deploy
</Button> </Button>
</NextLink> </Link>
<NextLink legacyBehavior href={gistId ? `/test/${gistId}` : '/test'} passHref shallow> <Link href={gistId ? `/test/${gistId}` : '/test'} passHref shallow>
<Button as="a" outline={!router.pathname.includes('/test')} uppercase> <Button as="a" outline={!router.pathname.includes('/test')} uppercase>
Test Test
</Button> </Button>
</NextLink> </Link>
</ButtonGroup> </ButtonGroup>
<NextLink legacyBehavior href="https://xrpl-hooks.readme.io/v2.0" passHref> <Link href="https://xrpl-hooks.readme.io/v2.0" passHref>
<a target="_blank" rel="noreferrer noopener"> <a target="_blank" rel="noreferrer noopener">
<Button outline> <Button outline>
<BookOpen size="15px" /> <BookOpen size="15px" />
</Button> </Button>
</a> </a>
</NextLink> </Link>
</Stack> </Stack>
</Flex> </Flex>
</Container> </Container>

View File

@@ -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,94 @@ 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',
width: '100%'
// eslint-disable-next-line react/display-name }
const Creatable = forwardRef<any, Props>((props, ref) => { },
const { theme } = useTheme() singleValue: provided => ({
const isDark = theme === 'dark' ...provided,
const styles = getStyles(isDark) color: colors.mauve12
return ( }),
<CreatableSelectInput menu: provided => ({
ref={ref} ...provided,
formatCreateLabel={label => `Enter "${label}"`} backgroundColor: colors.dropDownBg
menuPosition={props.menuPosition || 'fixed'} }),
styles={styles} 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
}
}
}
}}
{...props} {...props}
/> />
) )
}) })
export default styled(Select, {}) export default styled(Select, {})
export const CreatableSelect = styled(Creatable, {})

View File

@@ -43,6 +43,7 @@ const Transaction: FC<TransactionProps> = ({ header, state: txState, ...props })
(state: Partial<TransactionState> = txState) => { (state: Partial<TransactionState> = txState) => {
const { const {
selectedTransaction, selectedTransaction,
selectedDestAccount,
selectedAccount, selectedAccount,
txFields, txFields,
selectedFlags, selectedFlags,
@@ -51,19 +52,20 @@ const Transaction: FC<TransactionProps> = ({ header, state: txState, ...props })
} = state } = 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 Flags = combineFlags(selectedFlags?.map(flag => flag.value)) || txFields?.Flags
const HookParameters = Object.entries(hookParameters || {}).reduce< const HookParameters = Object.entries(hookParameters || {}).reduce<
SetHookData['HookParameters'] SetHookData['HookParameters']
>((acc, [_, { label, value }]) => { >((acc, [_, { label, value }]) => {
return acc.concat({ return acc.concat({
HookParameter: { HookParameterName: toHex(label), HookParameterValue: value } HookParameter: { HookParameterName: toHex(label), HookParameterValue: toHex(value) }
}) })
}, []) }, [])
const Memos = memos const Memos = memos
? Object.entries(memos).reduce<SetHookData['Memos']>((acc, [_, { format, data, type }]) => { ? Object.entries(memos).reduce<SetHookData['Memos']>((acc, [_, { format, data, type }]) => {
return acc?.concat({ return acc?.concat({
Memo: { MemoData: data, MemoFormat: toHex(format), MemoType: toHex(type) } Memo: { MemoData: toHex(data), MemoFormat: toHex(format), MemoType: toHex(type) }
}) })
}, []) }, [])
: undefined : undefined
@@ -73,6 +75,7 @@ const Transaction: FC<TransactionProps> = ({ header, state: txState, ...props })
HookParameters, HookParameters,
Flags, Flags,
TransactionType, TransactionType,
Destination,
Account, Account,
Memos Memos
}) })
@@ -125,12 +128,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) {
@@ -165,6 +167,13 @@ const Transaction: FC<TransactionProps> = ({ header, state: txState, ...props })
selectedTransaction: transactionType selectedTransaction: transactionType
} }
if (fields.Destination !== undefined) {
nwState.selectedDestAccount = null
fields.Destination = ''
} else {
fields.Destination = undefined
}
if (transactionType?.value && transactionFlags[transactionType?.value] && fields.Flags) { if (transactionType?.value && transactionFlags[transactionType?.value] && fields.Flags) {
nwState.selectedFlags = extractFlags(transactionType.value, fields.Flags) nwState.selectedFlags = extractFlags(transactionType.value, fields.Flags)
fields.Flags = undefined fields.Flags = undefined

View File

@@ -1,25 +1,25 @@
import { FC, ReactNode, useCallback, useEffect, useState } from 'react' import { FC, ReactNode, 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 { getFlags } from '../../state/constants/flags'
import { Plus, Trash } from 'phosphor-react' import { Plus, Trash } from 'phosphor-react'
import AccountSequence from '../Sequence' 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
@@ -37,14 +37,28 @@ export const TxUI: FC<UIProps> = ({
switchToJson switchToJson
}) => { }) => {
const { accounts } = useSnapshot(state) const { accounts } = useSnapshot(state)
const { selectedAccount, selectedTransaction, txFields, selectedFlags, hookParameters, memos } = const {
txState selectedAccount,
selectedDestAccount,
selectedTransaction,
txFields,
selectedFlags,
hookParameters,
memos
} = 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 destAccountOptions: SelectOption[] = accounts
.map(acc => ({
label: acc.name,
value: acc.address
}))
.filter(acc => acc.value !== selectedAccount?.value)
const flagsOptions: SelectOption[] = Object.entries( const flagsOptions: SelectOption[] = Object.entries(
getFlags(selectedTransaction?.value) || {} getFlags(selectedTransaction?.value) || {}
).map(([label, value]) => ({ ).map(([label, value]) => ({
@@ -73,22 +87,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)
@@ -121,23 +119,21 @@ export const TxUI: FC<UIProps> = ({
} }
}, [handleChangeTxType, selectedTransaction?.value]) }, [handleChangeTxType, selectedTransaction?.value])
const fields = useMemo(
() => getTxFields(selectedTransaction?.value),
[selectedTransaction?.value]
)
const richFields = ['TransactionType', 'Account', 'HookParameters', 'Memos'] const richFields = ['TransactionType', 'Account', 'HookParameters', 'Memos']
if (fields.Destination !== undefined) {
richFields.push('Destination')
}
if (flagsOptions.length) { if (flagsOptions.length) {
richFields.push('Flags') richFields.push('Flags')
} }
const otherFields = Object.keys(txFields).filter(k => !richFields.includes(k)) as [keyof TxFields] 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 ( return (
<Container <Container
css={{ css={{
@@ -169,6 +165,18 @@ export const TxUI: FC<UIProps> = ({
<TxField label="Sequence"> <TxField label="Sequence">
<AccountSequence address={selectedAccount?.value} /> <AccountSequence address={selectedAccount?.value} />
</TxField> </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') && ( {richFields.includes('Flags') && (
<TxField label="Flags"> <TxField label="Flags">
<Select <Select
@@ -190,133 +198,23 @@ export const TxUI: FC<UIProps> = ({
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}> <TxField key={field} label={field + (isXrp ? ' (XRP)' : '')}>
{isJson ? ( {isJson ? (
<Textarea <Textarea
rows={rows} rows={rows}
@@ -406,7 +304,7 @@ export const TxUI: FC<UIProps> = ({
/> />
<Input <Input
css={{ mx: '$2' }} css={{ mx: '$2' }}
placeholder="Value (hex-quoted)" placeholder="Value"
value={value} value={value}
onChange={e => { onChange={e => {
setState({ setState({
@@ -469,7 +367,7 @@ export const TxUI: FC<UIProps> = ({
}} }}
/> />
<Input <Input
placeholder="Data (hex-quoted)" placeholder="Data"
css={{ mx: '$2' }} css={{ mx: '$2' }}
value={memo.data} value={memo.data}
onChange={e => { onChange={e => {
@@ -527,35 +425,6 @@ 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 }> = ({ export const TxField: FC<{ label: string; children: ReactNode; multiLine?: boolean }> = ({
label, label,
children, children,
@@ -569,7 +438,7 @@ export const TxField: FC<{ label: string; children: ReactNode; multiLine?: boole
justifyContent: 'flex-end', justifyContent: 'flex-end',
alignItems: multiLine ? 'flex-start' : 'center', alignItems: multiLine ? 'flex-start' : 'center',
position: 'relative', position: 'relative',
mb: '$2', mb: '$3',
mt: '1px', mt: '1px',
pr: '1px' pr: '1px'
}} }}

View File

@@ -2,10 +2,7 @@
{ {
"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,
@@ -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",
@@ -64,10 +54,7 @@
{ {
"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",
@@ -139,18 +120,12 @@
"NFTokenID": "000100001E962F495F07A990F4ED55ACCFEEF365DBAA76B6A048C0A200000007", "NFTokenID": "000100001E962F495F07A990F4ED55ACCFEEF365DBAA76B6A048C0A200000007",
"Amount": { "Amount": {
"$value": "100", "$value": "100",
"$type": "amount.xrp" "$type": "xrp"
}, },
"Flags": "1", "Flags": "1",
"Destination": { "Destination": "",
"$type": "account", "Fee": "10",
"$value": "" "Owner": ""
},
"Owner": {
"$type": "account",
"$value": ""
},
"Fee": "10"
}, },
{ {
"TransactionType": "OfferCancel", "TransactionType": "OfferCancel",
@@ -168,29 +143,19 @@
"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",
@@ -201,12 +166,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,7 +182,7 @@
"Channel": "C1AE6DDDEEC05CF2978C0BAD6FE302948E9533691DC749DCDD3B9E5992CA6198", "Channel": "C1AE6DDDEEC05CF2978C0BAD6FE302948E9533691DC749DCDD3B9E5992CA6198",
"Amount": { "Amount": {
"$value": "200", "$value": "200",
"$type": "amount.xrp" "$type": "xrp"
}, },
"Expiration": 543171558, "Expiration": 543171558,
"Fee": "10" "Fee": "10"
@@ -276,7 +238,7 @@
"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",
@@ -287,10 +249,6 @@
}, },
{ {
"TransactionType": "Invoke", "TransactionType": "Invoke",
"Destination": {
"$type": "account",
"$value": ""
},
"Fee": "12" "Fee": "12"
}, },
{ {

View File

@@ -8,16 +8,8 @@ module.exports = {
config.resolve.alias['vscode'] = require.resolve( config.resolve.alias['vscode'] = require.resolve(
'@codingame/monaco-languageclient/lib/vscode-compatibility' '@codingame/monaco-languageclient/lib/vscode-compatibility'
) )
config.experiments = {
topLevelAwait: true,
layers: true
}
if (!isServer) { if (!isServer) {
config.resolve.fallback = { config.resolve.fallback.fs = false
...config.resolve.fallback,
fs: false,
module: false
}
} }
config.module.rules.push({ config.module.rules.push({
test: [/\.md$/, /hook-bundle\.js$/], test: [/\.md$/, /hook-bundle\.js$/],

View File

@@ -14,7 +14,6 @@
"dependencies": { "dependencies": {
"@codingame/monaco-jsonrpc": "^0.3.1", "@codingame/monaco-jsonrpc": "^0.3.1",
"@codingame/monaco-languageclient": "^0.17.0", "@codingame/monaco-languageclient": "^0.17.0",
"@eqlabs/assemblyscript": "^0.0.0-alpha.1680097351",
"@monaco-editor/react": "^4.4.5", "@monaco-editor/react": "^4.4.5",
"@octokit/core": "^3.5.1", "@octokit/core": "^3.5.1",
"@radix-ui/colors": "^0.1.7", "@radix-ui/colors": "^0.1.7",
@@ -38,7 +37,7 @@
"lodash.uniqby": "^4.7.0", "lodash.uniqby": "^4.7.0",
"lodash.xor": "^4.5.0", "lodash.xor": "^4.5.0",
"monaco-editor": "^0.33.0", "monaco-editor": "^0.33.0",
"next": "^13.1.1", "next": "^12.0.4",
"next-auth": "^4.10.3", "next-auth": "^4.10.3",
"next-plausible": "^3.2.0", "next-plausible": "^3.2.0",
"next-themes": "^0.1.1", "next-themes": "^0.1.1",
@@ -50,8 +49,8 @@
"postinstall-postinstall": "^2.1.0", "postinstall-postinstall": "^2.1.0",
"prettier": "^2.7.1", "prettier": "^2.7.1",
"re-resizable": "^6.9.1", "re-resizable": "^6.9.1",
"react": "^18.2.0", "react": "17.0.2",
"react-dom": "^18.2.0", "react-dom": "17.0.2",
"react-hook-form": "^7.28.0", "react-hook-form": "^7.28.0",
"react-hot-keys": "^2.7.1", "react-hot-keys": "^2.7.1",
"react-hot-toast": "^2.1.1", "react-hot-toast": "^2.1.1",
@@ -66,7 +65,7 @@
"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.32", "wabt": "^1.0.30",
"xrpl-accountlib": "^1.6.1", "xrpl-accountlib": "^1.6.1",
"xrpl-client": "^2.0.2" "xrpl-client": "^2.0.2"
}, },
@@ -79,7 +78,7 @@
"@types/react": "17.0.31", "@types/react": "17.0.31",
"browserify": "^17.0.0", "browserify": "^17.0.0",
"eslint": "7.32.0", "eslint": "7.32.0",
"eslint-config-next": "^13.1.1", "eslint-config-next": "11.1.2",
"raw-loader": "^4.0.2", "raw-loader": "^4.0.2",
"typescript": "4.4.4" "typescript": "4.4.4"
}, },

View File

@@ -24,7 +24,7 @@ import { ChatCircleText } from 'phosphor-react'
TimeAgo.setDefaultLocale(en.locale) TimeAgo.setDefaultLocale(en.locale)
TimeAgo.addLocale(en) TimeAgo.addLocale(en)
function MyApp({ Component, pageProps: { session, ...pageProps } }: AppProps<{ session?: any }>) { function MyApp({ Component, pageProps: { session, ...pageProps } }: AppProps) {
const router = useRouter() const router = useRouter()
const slug = router.query?.slug const slug = router.query?.slug
const gistId = (Array.isArray(slug) && slug[0]) ?? null const gistId = (Array.isArray(slug) && slug[0]) ?? null

View File

@@ -1,7 +1,7 @@
import type { NextRequest } from 'next/server' import type { NextRequest, NextFetchEvent } from 'next/server'
import { NextResponse as Response } from 'next/server' import { NextResponse as Response } from 'next/server'
export default function middleware(req: NextRequest) { export default function middleware(req: NextRequest, ev: NextFetchEvent) {
if (req.nextUrl.pathname === '/') { if (req.nextUrl.pathname === '/') {
const url = req.nextUrl.clone() const url = req.nextUrl.clone()
url.pathname = '/develop' url.pathname = '/develop'

View File

@@ -40,7 +40,7 @@ export default async function handler(
} }
return res.status(200).json(json) return res.status(200).json(json)
} catch (err) { } catch (err) {
console.error(err) console.log(err)
return res.status(500).json({ error: 'Server error' }) return res.status(500).json({ error: 'Server error' })
} }
return res.status(500).json({ error: 'Not able to create faucet, try again' }) return res.status(500).json({ error: 'Not able to create faucet, try again' })

View File

@@ -150,9 +150,7 @@ const Home: NextPage = () => {
const activeFile = snap.files[snap.active] as IFile | undefined const activeFile = snap.files[snap.active] as IFile | undefined
const activeFileExt = getFileExtention(activeFile?.name) const activeFileExt = getFileExtention(activeFile?.name)
const canCompile = activeFileExt === 'c' || activeFileExt === 'wat' || activeFileExt === 'ts' const canCompile = activeFileExt === 'c' || activeFileExt === 'wat'
const isCompiling = snap.compiling.includes(snap.active);
return ( return (
<Split <Split
direction="vertical" direction="vertical"
@@ -168,9 +166,7 @@ const Home: NextPage = () => {
{canCompile && ( {canCompile && (
<Hotkeys <Hotkeys
keyName="command+b,ctrl+b" keyName="command+b,ctrl+b"
onKeyDown={() => onKeyDown={() => !snap.compiling && snap.files.length && compileCode(snap.active)}
snap.compiling === undefined && snap.files.length && compileCode(snap.active)
}
> >
<Flex <Flex
css={{ css={{
@@ -187,7 +183,7 @@ const Home: NextPage = () => {
variant="primary" variant="primary"
uppercase uppercase
disabled={!snap.files.length} disabled={!snap.files.length}
isLoading={isCompiling} isLoading={snap.compiling}
onClick={() => compileCode(snap.active)} onClick={() => compileCode(snap.active)}
> >
<Play weight="bold" size="16px" /> <Play weight="bold" size="16px" />
@@ -204,9 +200,7 @@ const Home: NextPage = () => {
{activeFileExt === 'js' && ( {activeFileExt === 'js' && (
<Hotkeys <Hotkeys
keyName="command+b,ctrl+b" keyName="command+b,ctrl+b"
onKeyDown={() => onKeyDown={() => !snap.compiling && snap.files.length && compileCode(snap.active)}
!isCompiling && snap.files.length && compileCode(snap.active)
}
> >
<Flex <Flex
css={{ css={{

View File

@@ -1,49 +1,97 @@
import toast from 'react-hot-toast' import toast from 'react-hot-toast'
import Router from 'next/router' import Router from 'next/router'
import state, { IFile } from '../index' import state from '../index'
import { saveFile } from './saveFile' import { saveFile } from './saveFile'
import { decodeBinary } from '../../utils/decodeBinary' import { decodeBinary } from '../../utils/decodeBinary'
import { ref } from 'valtio' 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) => { 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] const file = state.files[activeId]
if (file.name.endsWith('.wat')) {
// Bail out if we're already compiling the file. return compileWat(activeId)
if (!file || state.compiling.includes(activeId)) {
return
} }
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.
return
}
// Set loading state to true
state.compiling = true
state.logs = []
try { try {
state.compiling.push(activeId)
state.logs = []
file.containsErrors = false 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!')
}
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
}
try {
// Decode base64 encoded wasm that is coming back from the endpoint
const bufferData = await decodeBinary(json.output)
let result: CompilationResult; // Import wabt from and create human readable version of wasm file and
if (file.name.endsWith('.wat')) { // put it into state
result = await compileWat(file); const ww = await (await import('wabt')).default()
} const myModule = ww.readWasm(new Uint8Array(bufferData), {
else if (file.language === "ts") { readDebugNames: true
result = await compileTs(file); })
} myModule.applyNames()
else if (navigator?.onLine === false) {
throw Error('You seem offline, check you internet connection and try again!') const wast = myModule.toText({ foldExprs: false, inlineExport: false })
}
else if (file.language === 'c') { file.compiledContent = ref(bufferData)
result = await compileC(file) file.lastCompiled = new Date()
} file.compiledValueSnapshot = file.content
else throw Error("Unknown file type.") file.compiledWatContent = wast
} catch (error) {
throw Error('Invalid compilation result produced, check your code for errors and try again!')
}
file.lastCompiled = new Date();
file.compiledValueSnapshot = file.content;
file.compiledContent = result.compiledContent;
file.compiledWatContent = result.compiledWatContent;
toast.success('Compiled successfully!', { position: 'bottom-center' }) toast.success('Compiled successfully!', { position: 'bottom-center' })
state.logs.push({ state.logs.push({
type: 'success', type: 'success',
@@ -52,8 +100,7 @@ export const compileCode = async (activeId: number) => {
linkText: 'Go to deploy' linkText: 'Go to deploy'
}) })
} catch (err) { } catch (err) {
console.error(err) console.log(err)
let message: string;
if (err instanceof Array && typeof err[0] === 'string') { if (err instanceof Array && typeof err[0] === 'string') {
err.forEach(message => { err.forEach(message => {
@@ -62,157 +109,63 @@ export const compileCode = async (activeId: number) => {
message message
}) })
}) })
message = "Compilation errors occurred, see logs for more info."
} else if (err instanceof Error) { } else if (err instanceof Error) {
message = err.message state.logs.push({
type: 'error',
message: err.message
})
} else { } else {
message = 'Something went wrong, try again later!' state.logs.push({
type: 'error',
message: 'Something went wrong, come back later!'
})
} }
toast.error(message, { position: 'bottom-center' }) state.compiling = false
toast.error(`Error occurred while compiling!`, { position: 'bottom-center' })
file.containsErrors = true file.containsErrors = true
} }
state.compiling = state.compiling.filter(id => id !== activeId);
} }
/* compileC sends the code of the active file to compile endpoint export const compileWat = async (activeId: number) => {
* If all goes well you will get base64 encoded wasm file back with if (state.compiling) return;
* some extra logging information if we can provide it. This function const file = state.files[activeId]
* also decodes the returned wasm and creates human readable WAT file state.compiling = true
* out of it and store both in global state. state.logs = []
*/
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 { try {
// Decode base64 encoded wasm that is coming back from the endpoint const wabt = await (await import('wabt')).default()
const bufferData = await decodeBinary(json.output) const module = wabt.parseWat(file.name, file.content);
module.resolveNames();
// Import wabt from and create human readable version of wasm file and module.validate();
// put it into state const { buffer } = module.toBinary({
const ww = await (await import('wabt')).default() log: false,
const myModule = ww.readWasm(new Uint8Array(bufferData), { write_debug_names: true,
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 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?: ...,
}); });
let logMsg = stdout.toString() file.compiledContent = ref(buffer)
let errMsg = stderr.toString() file.lastCompiled = new Date()
if (logMsg) { file.compiledValueSnapshot = file.content
state.logs.push({ file.compiledWatContent = file.content
type: "log",
message: logMsg
})
}
if (errMsg) {
state.logs.push({
type: "error",
message: errMsg
})
}
if (error) return reject(error) 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
} }

View File

@@ -1,8 +1,8 @@
import { getFileExtention } from '../../utils/helpers' import { getFileExtention } from '../../utils/helpers'
import state, { IFile, ILang } from '../index' import state, { IFile } from '../index'
const languageMapping: Record<string, ILang | undefined> = { const languageMapping: Record<string, string | undefined> = {
ts: 'ts', ts: 'typescript',
js: 'javascript', js: 'javascript',
md: 'markdown', md: 'markdown',
c: 'c', c: 'c',

View File

@@ -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
})
} }

View File

@@ -247,7 +247,7 @@ export const deleteHook = async (account: IAccount & { name?: string }) => {
}) })
} }
} catch (err) { } catch (err) {
console.error(err) console.log(err)
toast.error('Error occurred while deleting hook', { id: toastId }) toast.error('Error occurred while deleting hook', { id: toastId })
state.deployLogs.push({ state.deployLogs.push({
type: 'error', type: 'error',

View File

@@ -1,5 +1,5 @@
import { Octokit } from '@octokit/core' import { Octokit } from '@octokit/core'
import state, { IFile, ILang } from '../index' import state, { IFile } from '../index'
import { templateFileIds } from '../constants' import { templateFileIds } from '../constants'
const octokit = new Octokit() const octokit = new Octokit()
@@ -48,7 +48,7 @@ export const fetchFiles = async (gistId: string) => {
const files: IFile[] = Object.keys(res.data.files).map(filename => ({ const files: IFile[] = Object.keys(res.data.files).map(filename => ({
name: res.data.files?.[filename]?.filename || 'untitled.c', name: res.data.files?.[filename]?.filename || 'untitled.c',
language: res.data.files?.[filename]?.language?.toLowerCase() as ILang | undefined, language: res.data.files?.[filename]?.language?.toLowerCase() || '',
content: res.data.files?.[filename]?.content || '' content: res.data.files?.[filename]?.content || ''
})) }))

View File

@@ -10,6 +10,7 @@ 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 {

View File

@@ -55,7 +55,7 @@ export const syncToGist = async (session?: Session | null, createNewGist?: boole
return toast.success('Updated to gist successfully!', { id: toastId }) return toast.success('Updated to gist successfully!', { id: toastId })
}) })
.catch(err => { .catch(err => {
console.error(err) console.log(err)
state.gistLoading = false state.gistLoading = false
return toast.error(`Could not update Gist, try again later!`, { return toast.error(`Could not update Gist, try again later!`, {
id: toastId id: toastId
@@ -85,7 +85,7 @@ export const syncToGist = async (session?: Session | null, createNewGist?: boole
return toast.success('Created new gist successfully!', { id: toastId }) return toast.success('Created new gist successfully!', { id: toastId })
}) })
.catch(err => { .catch(err => {
console.error(err) console.log(err)
state.gistLoading = false state.gistLoading = false
return toast.error(`Could not create Gist, try again later!`, { return toast.error(`Could not create Gist, try again later!`, {
id: toastId id: toastId

View File

@@ -9,10 +9,9 @@ declare module 'valtio' {
function snapshot<T extends object>(p: T): T function snapshot<T extends object>(p: T): T
} }
export type ILang = "ts" | "javascript" | "markdown" | "c" | "text"
export interface IFile { export interface IFile {
name: string name: string
language: ILang | undefined language: string
content: string content: string
compiledValueSnapshot?: string compiledValueSnapshot?: string
compiledContent?: ArrayBuffer | null compiledContent?: ArrayBuffer | null
@@ -67,7 +66,7 @@ export interface IState {
loading: boolean loading: boolean
gistLoading: boolean gistLoading: boolean
zipLoading: boolean zipLoading: boolean
compiling: /* file id */ number[] compiling: boolean
logs: ILog[] logs: ILog[]
deployLogs: ILog[] deployLogs: ILog[]
transactionLogs: ILog[] transactionLogs: ILog[]
@@ -99,7 +98,7 @@ let initialState: IState = {
// Active file index on the Deploy page editor // Active file index on the Deploy page editor
activeWat: 0, activeWat: 0,
loading: false, loading: false,
compiling: [], compiling: false,
logs: [], logs: [],
deployLogs: [], deployLogs: [],
transactionLogs: [], transactionLogs: [],
@@ -136,7 +135,7 @@ if (typeof window !== 'undefined') {
try { try {
localStorageAccounts = localStorage.getItem('hooksIdeAccounts') localStorageAccounts = localStorage.getItem('hooksIdeAccounts')
} catch (err) { } catch (err) {
console.error(`localStorage state broken`) console.log(`localStorage state broken`)
localStorage.removeItem('hooksIdeAccounts') localStorage.removeItem('hooksIdeAccounts')
} }
if (localStorageAccounts) { if (localStorageAccounts) {

View File

@@ -6,7 +6,6 @@ import { showAlert } from '../state/actions/showAlert'
import { parseJSON } from '../utils/json' import { parseJSON } from '../utils/json'
import { extractFlags, getFlags } from './constants/flags' import { extractFlags, getFlags } from './constants/flags'
import { fromHex } from '../utils/setHook' import { fromHex } from '../utils/setHook'
import { typeIs } from '../utils/helpers'
export type SelectOption = { export type SelectOption = {
value: string value: string
@@ -28,6 +27,7 @@ export type Memos = {
export interface TransactionState { export interface TransactionState {
selectedTransaction: SelectOption | null selectedTransaction: SelectOption | null
selectedAccount: SelectOption | null selectedAccount: SelectOption | null
selectedDestAccount: SelectOption | null
selectedFlags: SelectOption[] | null selectedFlags: SelectOption[] | null
hookParameters: HookParameters hookParameters: HookParameters
memos: Memos memos: Memos
@@ -50,6 +50,7 @@ export type TxFields = Omit<
export const defaultTransaction: TransactionState = { export const defaultTransaction: TransactionState = {
selectedTransaction: null, selectedTransaction: null,
selectedAccount: null, selectedAccount: null,
selectedDestAccount: null,
selectedFlags: null, selectedFlags: null,
hookParameters: {}, hookParameters: {},
memos: {}, memos: {},
@@ -129,51 +130,46 @@ 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 +179,7 @@ export const prepareState = (value: string, transactionType?: string) => {
return return
} }
const { Account, TransactionType, HookParameters, Memos, ...rest } = options const { Account, TransactionType, Destination, HookParameters, Memos, ...rest } = options
let tx: Partial<TransactionState> = {} let tx: Partial<TransactionState> = {}
const schema = getTxFields(transactionType) const schema = getTxFields(transactionType)
@@ -215,7 +211,7 @@ export const prepareState = (value: string, transactionType?: string) => {
if (HookParameters && HookParameters instanceof Array) { if (HookParameters && HookParameters instanceof Array) {
tx.hookParameters = HookParameters.reduce<TransactionState["hookParameters"]>((acc, cur, idx) => { tx.hookParameters = HookParameters.reduce<TransactionState["hookParameters"]>((acc, cur, idx) => {
const param = { label: fromHex(cur.HookParameter?.HookParameterName || ""), value: cur.HookParameter?.HookParameterValue || "" } const param = { label: fromHex(cur.HookParameter?.HookParameterName || ""), value: fromHex(cur.HookParameter?.HookParameterValue || "") }
acc[idx] = param; acc[idx] = param;
return acc; return acc;
}, {}) }, {})
@@ -223,12 +219,30 @@ export const prepareState = (value: string, transactionType?: string) => {
if (Memos && Memos instanceof Array) { if (Memos && Memos instanceof Array) {
tx.memos = Memos.reduce<TransactionState["memos"]>((acc, cur, idx) => { tx.memos = Memos.reduce<TransactionState["memos"]>((acc, cur, idx) => {
const memo = { data: cur.Memo?.MemoData || "", type: fromHex(cur.Memo?.MemoType || ""), format: fromHex(cur.Memo?.MemoFormat || "") } const memo = { data: fromHex(cur.Memo?.MemoData || ""), type: fromHex(cur.Memo?.MemoType || ""), format: fromHex(cur.Memo?.MemoFormat || "") }
acc[idx] = memo; acc[idx] = memo;
return acc; 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) { if (getFlags(TransactionType) && rest.Flags) {
const flags = extractFlags(TransactionType, rest.Flags) const flags = extractFlags(TransactionType, rest.Flags)
@@ -240,31 +254,17 @@ export const prepareState = (value: string, transactionType?: string) => {
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

View File

@@ -24,9 +24,6 @@ const estimateFee = async (
const { signedTransaction } = sign(copyTx, keypair) const { signedTransaction } = sign(copyTx, keypair)
const res = await xrplSend({ command: 'fee', tx_blob: signedTransaction }) const res = await xrplSend({ 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 +31,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
} }

View File

@@ -19,23 +19,3 @@ export const getFileExtention = (filename?: string): string | undefined => {
const ext = (filename.includes('.') && filename.split('.').pop()) || undefined const ext = (filename.includes('.') && filename.split('.').pop()) || undefined
return ext 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;
}

View File

@@ -30,7 +30,7 @@ export const tts = {
export type TTS = typeof tts export type TTS = typeof tts
const calculateHookOn = (arr: (keyof TTS)[]) => { const calculateHookOn = (arr: (keyof TTS)[]) => {
let s = '0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffbfffff' let s = '0x3e3ff5bf'
arr.forEach(n => { arr.forEach(n => {
let v = BigInt(s) let v = BigInt(s)
v ^= BigInt(1) << BigInt(tts[n]) v ^= BigInt(1) << BigInt(tts[n])

View File

@@ -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 {

View File

@@ -85,8 +85,7 @@ export const getInvokeOptions = (content?: string) => {
export function toHex(str: string) { export function toHex(str: string) {
var result = '' var result = ''
for (var i = 0; i < str.length; i++) { for (var i = 0; i < str.length; i++) {
const hex = str.charCodeAt(i).toString(16) result += str.charCodeAt(i).toString(16)
result += hex.padStart(2, '0')
} }
return result.toUpperCase() return result.toUpperCase()
} }

1374
yarn.lock

File diff suppressed because it is too large Load Diff