Compare commits

...

103 Commits

Author SHA1 Message Date
Denis Angell
8f52873065 fix transactions (#320) 2025-02-28 10:21:53 +01:00
Denis Angell
1b1d18ebaf remove LastLedgerSequence 2025-02-28 10:14:25 +01:00
Ekiserrepé
4c6a0b5a40 Bugs and Discussions button was broken, new link! (#318) 2025-02-28 10:10:59 +01:00
Tristan
0616149b6c New xahau share image 2025-02-18 09:45:56 +01:00
tequ
ee86d657c9 bump next-auth, typescript (#314) 2025-02-13 14:43:21 +01:00
tequ
12ebd4c1bc Fixed to retrieve the template header file from gist (#316) 2025-02-13 14:32:17 +01:00
Wietse Wind
6ed0b54fc5 Merge pull request #310 from Ekiserrepe/main
Readme and xah value updated
2025-01-02 01:21:03 +01:00
Ekiserrepé
b7e1343055 Github link update 2025-01-01 23:29:04 +01:00
Ekiserrepé
a13dbaeb53 Merge branch 'Xahau:main' into main 2025-01-01 23:14:23 +01:00
Ekiserrepé
98a7a1ee72 Readme updated 2025-01-01 22:22:26 +01:00
Ekiserrepé
46c0ec9682 value xah updated 2025-01-01 22:20:33 +01:00
Wietse Wind
fbc27b58c3 Merge pull request #309 from Ekiserrepe/main
Xahau translation
2025-01-01 22:07:02 +01:00
Ekiserrepé
635e1e22aa Update XAH capitals 2025-01-01 21:29:35 +01:00
Ekiserrepé
62ca0603fc Xahau translation 2025-01-01 18:03:13 +01:00
Wietse Wind
3234ba851a Merge pull request #308 from XRPLF/hooks-xahau-endpoints
update explorer & wss network URLS @ env sample
2023-10-30 23:06:27 +01:00
Wietse Wind
26aeea5a0d update explorer & wss network URLS @ env sample 2023-10-30 23:04:53 +01:00
Wietse Wind
4a1bd98bd4 Merge pull request #306 from Transia-RnD/main
Fix Bad URIToken Amount
2023-08-01 05:32:33 +02:00
Denis Angell
4171e618a2 add uritoken flag 2023-08-01 05:31:02 +02:00
Denis Angell
1860b2d9ce fix bad amount 2023-08-01 05:23:25 +02:00
Denis Angell
488828f9d6 remove nft transactions 2023-08-01 05:23:09 +02:00
Wietse Wind
9f2105f6d3 Merge pull request #305 from Transia-RnD/main
Add URIToken features
2023-08-01 05:11:58 +02:00
Denis Angell
6beccaef68 update ripple-binary-codec 2023-06-19 16:55:25 +00:00
Denis Angell
215af7258c Merge branch 'main' into main 2023-06-15 07:04:13 +00:00
Denis Angell
ac3137088b remove default hookon 2023-06-15 07:01:15 +00:00
Denis Angell
1b8debda87 bump ripple-binary-codec & update hook on 2023-06-15 06:35:26 +00:00
Wietse Wind
c50c7a5860 Merge pull request #304 from Transia-RnD/main
Update readme link
2023-06-11 08:34:34 +02:00
Denis Angell
d21cda21d0 !fixup 2023-06-06 12:53:43 +00:00
Denis Angell
1cae0f161e change readme link 2023-06-06 12:28:19 +00:00
muzamil
412e3f2bbf Merge pull request #303 from XRPLF/fix/hex-value
Take tx memo data and parameter value as hex.
2023-03-29 17:43:11 +05:30
muzam1l
dc37b1911a Take tx memo data and parameter value as hex. 2023-03-29 15:04:14 +05:30
muzamil
c348868c89 Merge pull request #302 from XRPLF/fix/hook-on
Fix HookOn initial value.
2023-03-28 16:28:35 +05:30
muzam1l
2c3cfebe3a Fix HookOn initial value. 2023-03-28 15:14:43 +05:30
muzamil
6265a9cdbf Merge pull request #300 from XRPLF/fix/to-hex
Fix hex logic.
2023-03-27 21:20:08 +05:30
muzamil
1321b498cf Merge pull request #299 from XRPLF/fix/invoke-tx
Add `Destination` field to Invoke transaction.
2023-03-27 21:18:35 +05:30
muzam1l
801d9778cb Fix hex logic. 2023-03-27 19:21:14 +05:30
muzam1l
2cf18ef61c Add Destination field to Invoke transaction. 2023-03-27 15:32:25 +05:30
muzamil
4d2dc16ce5 Merge pull request #296 from XRPLF/feat/account-ui
Extend transactions Account UI.
2023-03-23 20:45:27 +05:30
muzam1l
9ecf5478e6 Remove tx Destination default values. 2023-03-23 19:39:56 +05:30
muzam1l
6d1ef110b7 Token Issuer account UI. 2023-03-23 17:52:17 +05:30
muzam1l
b9edfcd63b Fix: nextjs build. 2023-03-23 16:55:32 +05:30
muzam1l
b653d9a9cb Fix: Remove last traces of custom Destination handling! 2023-03-23 16:47:25 +05:30
muzam1l
da28e0a7d1 Use "creatable select" for accounts. 2023-03-23 16:45:24 +05:30
muzam1l
8a5b83d57f Fix: Don't remove fields in JSON mode if empty. 2023-03-23 16:26:29 +05:30
muzam1l
025eff6cf2 Add Select UI for account. 2023-03-23 16:12:35 +05:30
muzam1l
62d521b2cc Add owner field to NFTokenCreateOffer. 2023-03-23 15:51:36 +05:30
muzam1l
7aafca21df Remove Destination as special field. 2023-03-23 15:49:17 +05:30
muzamil
80f58e903c Merge pull request #294 from XRPLF/feat/amount-ui
Transaction amount UI.
2023-03-20 14:04:30 +05:30
muzam1l
c4af3df017 Add DeliverMin field to tx CheckCash. 2023-03-17 20:10:57 +05:30
muzam1l
5d8d142bc4 Catch all estimate fee errors. 2023-03-17 19:01:32 +05:30
muzam1l
e27a71d713 Estimate fee correct error message and amount input type. 2023-03-17 18:57:03 +05:30
muzam1l
e08b07cbeb Update tx OfferCreate. 2023-03-17 18:03:37 +05:30
muzam1l
e4936c03ef Css changes. 2023-03-17 17:17:29 +05:30
muzam1l
21a69ac8ea minor type fix. 2023-03-17 16:59:12 +05:30
muzam1l
52e4f219f7 Transaction amount UI. 2023-03-17 16:49:00 +05:30
muzamil
e1f34c4beb Merge pull request #291 from XRPLF/fix/sequence-ui
Fix Account sequence UI reset.
2023-03-14 21:30:35 +05:30
muzam1l
54a89c969e Fix account sequence reset. 2023-03-14 15:38:09 +05:30
muzamil
ded867d997 Merge pull request #289 from XRPLF/fix/tx
Account sequence.
2023-03-10 17:31:55 +05:30
muzam1l
0fce9af77c Fetch sequence on account creation. 2023-03-10 16:18:35 +05:30
muzam1l
55c68c580a Account Sequence UI. 2023-03-10 16:02:32 +05:30
muzamil
832a7997d1 Merge pull request #288 from XRPLF/fix/tx
Fix tx json saving and discarding.
2023-03-08 21:32:36 +05:30
muzam1l
4528e5a16e Actually fix Json 'save'. 2023-03-08 20:33:23 +05:30
muzam1l
38f064c6d8 Fix json saving and discarding. 2023-03-08 17:13:36 +05:30
muzamil
fbf4565dbc Merge pull request #287 from XRPLF/feat/memos-ui
Memos UI
2023-03-07 17:32:32 +05:30
muzam1l
9001c64fed minor label changes. 2023-03-07 16:58:32 +05:30
muzam1l
03b768db4e UI for memos fields. 2023-03-07 16:03:47 +05:30
muzam1l
825af0db89 Add memos field in transactions. 2023-03-06 21:14:14 +05:30
muzamil
31043f33ab Merge pull request #286 from XRPLF/feat/tx-params-ui
HookParameters UI for transactions.
2023-03-06 16:06:19 +05:30
muzam1l
39699a1cb9 HookParameters UI for transactions. 2023-03-03 18:32:03 +05:30
muzam1l
b50b300307 Refactor tx. 2023-03-03 16:00:43 +05:30
muzamil
82c06cbb12 Merge pull request #285 from XRPLF/fix/params
Add invoke hookon option.
2023-03-02 19:17:38 +05:30
muzam1l
423ee18e6a Add Invoke HookOn option. 2023-03-02 15:11:44 +05:30
muzamil
3bb26d0c9b Merge pull request #281 from XRPLF/feat/testnet-v3
Testnet v3.
2023-02-10 16:12:22 +05:30
muzam1l
43c83d0de6 Fix delete hook and some refactor. 2023-02-10 14:43:51 +05:30
muzam1l
6bb407cb0f Better package loading experience. 2023-02-09 22:15:22 +05:30
muzamil
d7b29ba809 Merge pull request #282 from XRPLF/feat/bundle-xrpl
Bundle patched `xrpl-accountlib` and expose through require syntax.
2023-02-09 20:22:43 +05:30
muzam1l
ca81d8ad41 Bundle xrpl-accountlib and expose throw require syntax. 2023-02-09 15:41:06 +05:30
muzam1l
a7e59d7b73 Add UriToken transaction. 2023-02-07 14:32:06 +05:30
muzam1l
8f6b28cef5 Add Invoke transaction type. 2023-02-07 14:15:07 +05:30
muzam1l
c1815f272b refactor. 2023-02-07 14:00:57 +05:30
muzam1l
76871a8041 Testnet v3. 2023-02-06 21:09:11 +05:30
mariopil
d2033c8035 Hooks docs update (#279)
* Added hooks-guard-call-non-const checker doc, updated hooks-guard-in-for doc.
2023-01-09 13:43:32 +01:00
muzamil
48daf1c5c8 Merge pull request #278 from XRPLF/feat/compile-wat
Increase wat file priority in sorting.
2022-12-07 15:03:40 +05:30
muzam1l
a3365e4beb Increase wat file priority in sorting. 2022-12-07 15:02:09 +05:30
muzamil
45d813cdad Merge pull request #277 from XRPLF/feat/compile-wat
Compile raw wat files.
2022-12-07 14:46:05 +05:30
muzam1l
911416aa2f Compile wat files. 2022-12-07 13:31:59 +05:30
muzamil
2d836af9ed Merge pull request #276 from XRPLF/feat/flags-ui
Transaction flags UI.
2022-11-01 17:14:44 +05:30
muzam1l
4f0fc838be Minor css fix. 2022-11-01 11:20:09 +05:30
muzam1l
fa93912c38 Remove reductant comment. 2022-10-28 14:58:11 +05:30
muzam1l
f5cb76c302 Merge branch 'main' into feat/flags-ui 2022-10-28 14:48:41 +05:30
muzamil
df1d65dcab Merge pull request #275 from XRPLF/dependabot/npm_and_yarn/jose-4.10.0
Bump jose from 4.6.0 to 4.10.0
2022-10-28 14:48:02 +05:30
muzam1l
1513f78991 Add tx specific flags. 2022-10-28 14:43:37 +05:30
muzam1l
3a064f307b Add global flags. 2022-10-28 14:21:22 +05:30
muzam1l
6fca05f310 Add flags UI to Payment transaction. 2022-10-28 12:29:57 +05:30
muzamil
31e67d382f Merge pull request #273 from XRPLF/fix/renaming-ext
Update file language on renaming.
2022-10-26 18:10:43 +05:30
dependabot[bot]
27475301e4 Bump jose from 4.6.0 to 4.10.0
Bumps [jose](https://github.com/panva/jose) from 4.6.0 to 4.10.0.
- [Release notes](https://github.com/panva/jose/releases)
- [Changelog](https://github.com/panva/jose/blob/main/CHANGELOG.md)
- [Commits](https://github.com/panva/jose/compare/v4.6.0...v4.10.0)

---
updated-dependencies:
- dependency-name: jose
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-10-14 08:29:14 +00:00
muzamil
2d9ca2674e Merge pull request #266 from XRPLF/feat/engine-code-links
Linkify engine error codes and some refactor.
2022-08-19 20:00:22 +05:30
muzam1l
221c727af6 Fix mistake in set-codes json. 2022-08-19 18:33:31 +05:30
muzam1l
3b0a8c44c9 Remove link title from log. 2022-08-19 17:46:02 +05:30
muzam1l
094f739b80 Merge branch 'main' into feat/engine-code-links 2022-08-19 15:07:44 +05:30
muzam1l
2d82966b3b Tooltip for SetHook codes in debug stream. 2022-08-16 18:16:49 +05:30
muzam1l
93c5ef231e Fix display of nullish values in script logs. 2022-08-09 17:18:22 +05:30
muzam1l
53c2104b94 Point groups of error codes to their individual pages. 2022-08-09 17:00:30 +05:30
muzam1l
c336ff8334 Linkify engine error codes and some refactor. 2022-08-09 02:07:15 +05:30
96 changed files with 3380 additions and 2958 deletions

View File

@@ -5,7 +5,8 @@ GITHUB_ID=""
NEXT_PUBLIC_COMPILE_API_ENDPOINT="http://localhost:9000/api/build"
NEXT_PUBLIC_COMPILE_API_BASE_URL="http://localhost:9000"
NEXT_PUBLIC_LANGUAGE_SERVER_API_ENDPOINT="ws://localhost:9000/language-server/c"
NEXT_PUBLIC_TESTNET_URL="hooks-testnet-v2.xrpl-labs.com"
NEXT_PUBLIC_DEBUG_STREAM_URL="hooks-testnet-v2-debugstream.xrpl-labs.com"
NEXT_PUBLIC_EXPLORER_URL="hooks-testnet-v2-explorer.xrpl-labs.com"
NEXT_PUBLIC_SITE_URL=http://localhost:3000
NEXT_PUBLIC_TESTNET_URL="xahau-test.net"
NEXT_PUBLIC_DEBUG_STREAM_URL="xahau-test.net/debugstream"
NEXT_PUBLIC_EXPLORER_URL="explorer.xahau-test.net"
NEXT_PUBLIC_NETWORK_ID="21338"
NEXT_PUBLIC_SITE_URL="http://localhost:3000"

4
.gitignore vendored
View File

@@ -33,3 +33,7 @@ yarn-error.log*
# vercel
.vercel
.vscode
# yarn
.yarnrc.yml
.yarn/

View File

@@ -1,8 +1,8 @@
# XRPL Hooks Builder
# Xahau Hooks Builder
https://hooks-builder.xrpl.org/
https://builder.xahau.network/
This is the repository for XRPL Hooks Builder. This project is built with Next.JS
This is the repository for Xahau Hooks Builder. This project is built with Next.JS
## General

View File

@@ -33,6 +33,7 @@ import { addFunds } from '../state/actions/addFaucetAccount'
import { deleteHook } from '../state/actions/deployHook'
import { capitalize } from '../utils/helpers'
import { deleteAccount } from '../state/actions/deleteAccount'
import { xrplSend } from '../state/actions/xrpl-client'
export const AccountDialog = ({
activeAccountAddress,
@@ -199,7 +200,7 @@ export const AccountDialog = ({
.toUnit()
.toLocaleString(undefined, {
style: 'currency',
currency: 'XRP',
currency: 'XAH',
currencyDisplay: 'name'
})}
<Button
@@ -301,7 +302,7 @@ const Accounts: FC<AccountProps> = props => {
const fetchAccInfo = async () => {
if (snap.clientStatus === 'online') {
const requests = snap.accounts.map(acc =>
snap.client?.send({
xrplSend({
id: `hooks-builder-req-info-${acc.address}`,
command: 'account_info',
account: acc.address
@@ -329,7 +330,7 @@ const Accounts: FC<AccountProps> = props => {
}
})
const objectRequests = snap.accounts.map(acc => {
return snap.client?.send({
return xrplSend({
id: `hooks-builder-req-objects-${acc.address}`,
command: 'account_objects',
account: acc.address
@@ -458,7 +459,7 @@ const Accounts: FC<AccountProps> = props => {
.toUnit()
.toLocaleString(undefined, {
style: 'currency',
currency: 'XRP',
currency: 'XAH',
currencyDisplay: 'name'
})})`
) : (

View File

@@ -5,6 +5,7 @@ import { subscribeKey } from 'valtio/utils'
import { Select } from '.'
import state, { ILog, transactionsState } from '../state'
import { extractJSON } from '../utils/json'
import EnrichLog from './EnrichLog'
import LogBox from './LogBox'
interface ISelect<T = string> {
@@ -99,6 +100,11 @@ const addListeners = (account: ISelect | null) => {
subscribeKey(streamState, 'selectedAccount', addListeners)
const clearLog = () => {
streamState.logs = []
streamState.statusChangeTimestamp = Date.now()
}
const DebugStream = () => {
const { selectedAccount, logs } = useSnapshot(streamState)
const { activeHeader: activeTxTab } = useSnapshot(transactionsState)
@@ -134,11 +140,6 @@ const DebugStream = () => {
streamState.selectedAccount = account
}, [activeTxTab])
const clearLog = () => {
streamState.logs = []
streamState.statusChangeTimestamp = Date.now()
}
return (
<LogBox enhanced renderNav={renderNav} title="Debug stream" logs={logs} clearLog={clearLog} />
)
@@ -157,9 +158,11 @@ export const pushLog = (str: any, opts: Partial<Pick<ILog, 'type'>> = {}): ILog
const timestring = !timestamp ? tm : new Date(timestamp).toLocaleTimeString()
const extracted = extractJSON(msg)
const message = !extracted ? msg : msg.slice(0, extracted.start) + msg.slice(extracted.end + 1)
const _message = !extracted ? msg : msg.slice(0, extracted.start) + msg.slice(extracted.end + 1)
const message = ref(<EnrichLog str={_message} />)
const jsonData = extracted ? JSON.stringify(extracted.result, null, 2) : undefined
const _jsonData = extracted ? JSON.stringify(extracted.result, null, 2) : undefined
const jsonData = _jsonData ? ref(<EnrichLog str={_jsonData} />) : undefined
if (extracted?.result?.id?._Request?.includes('hooks-builder-req')) {
return

View File

@@ -15,7 +15,7 @@ const contentShow = keyframes({
'100%': { opacity: 1 }
})
const StyledOverlay = styled(DialogPrimitive.Overlay, {
zIndex: 10000,
zIndex: 3000,
backgroundColor: blackA.blackA9,
position: 'fixed',
inset: 0,

73
components/EnrichLog.tsx Normal file
View File

@@ -0,0 +1,73 @@
import { FC, useState } from 'react'
import regexifyString from 'regexify-string'
import { useSnapshot } from 'valtio'
import { Link } from '.'
import state from '../state'
import { AccountDialog } from './Accounts'
import Tooltip from './Tooltip'
import hookSetCodes from '../content/hook-set-codes.json'
import { capitalize } from '../utils/helpers'
interface EnrichLogProps {
str?: string
}
const EnrichLog: FC<EnrichLogProps> = ({ str }) => {
const { accounts } = useSnapshot(state)
const [dialogAccount, setDialogAccount] = useState<string | null>(null)
if (!str || !accounts.length) return <>{str}</>
const addrs = accounts.map(acc => acc.address)
const regex = `(${addrs.join('|')}|HookSet\\(\\d+\\))`
const res = regexifyString({
pattern: new RegExp(regex, 'gim'),
decorator: (match, idx) => {
if (match.startsWith('r')) {
// Account
const name = accounts.find(acc => acc.address === match)?.name
return (
<Link
key={match + idx}
as="a"
onClick={() => setDialogAccount(match)}
title={match}
highlighted
>
{name || match}
</Link>
)
}
if (match.startsWith('HookSet')) {
const code = match.match(/^HookSet\((\d+)\)/)?.[1]
const val = hookSetCodes.find(v => code && v.code === +code)
console.log({ code, val })
if (!val) return match
const content = capitalize(val.description) || 'No hint available!'
return (
<>
HookSet(
<Tooltip content={content}>
<Link>{val.identifier}</Link>
</Tooltip>
)
</>
)
}
return match
},
input: str
})
return (
<>
{res}
<AccountDialog
setActiveAccountAddress={setDialogAccount}
activeAccountAddress={dialogAccount}
/>
</>
)
}
export default EnrichLog

View File

@@ -1,15 +1,12 @@
import { useRef, useLayoutEffect, ReactNode, FC, useState, useCallback } from 'react'
import { useRef, useLayoutEffect, ReactNode, FC, useState } from 'react'
import { IconProps, Notepad, Prohibit } from 'phosphor-react'
import useStayScrolled from 'react-stay-scrolled'
import NextLink from 'next/link'
import Container from './Container'
import LogText from './LogText'
import state, { ILog } from '../state'
import { ILog } from '../state'
import { Pre, Link, Heading, Button, Text, Flex, Box } from '.'
import regexifyString from 'regexify-string'
import { useSnapshot } from 'valtio'
import { AccountDialog } from './Accounts'
interface ILogBox {
title: string
@@ -143,70 +140,25 @@ const LogBox: FC<ILogBox> = ({
export const Log: FC<ILog> = ({
type,
timestring,
message: _message,
message,
link,
linkText,
defaultCollapsed,
jsonData: _jsonData
jsonData
}) => {
const [expanded, setExpanded] = useState(!defaultCollapsed)
const { accounts } = useSnapshot(state)
const [dialogAccount, setDialogAccount] = useState<string | null>(null)
const enrichAccounts = useCallback(
(str?: string): ReactNode => {
if (!str || !accounts.length) return str
const pattern = `(${accounts.map(acc => acc.address).join('|')})`
const res = regexifyString({
pattern: new RegExp(pattern, 'gim'),
decorator: (match, idx) => {
const name = accounts.find(acc => acc.address === match)?.name
return (
<Link
key={match + idx}
as="a"
onClick={() => setDialogAccount(match)}
title={match}
highlighted
>
{name || match}
</Link>
)
},
input: str
})
return <>{res}</>
},
[accounts]
)
let message: ReactNode
if (typeof _message === 'string') {
_message = _message.trim().replace(/\n /gi, '\n')
if (_message) message = enrichAccounts(_message)
else message = <Text muted>{'""'}</Text>
} else {
message = _message
}
const jsonData = enrichAccounts(_jsonData)
if (message === undefined) message = <Text muted>{'undefined'}</Text>
else if (message === '') message = <Text muted>{'""'}</Text>
return (
<>
<AccountDialog
setActiveAccountAddress={setDialogAccount}
activeAccountAddress={dialogAccount}
/>
<LogText variant={type}>
{timestring && (
<Text muted monospace>
{timestring}{' '}
</Text>
)}
<Pre>{message} </Pre>
<Pre>{message}</Pre>
{link && (
<NextLink href={link} shallow passHref>
<Link as="a">{linkText}</Link>
@@ -219,7 +171,6 @@ export const Log: FC<ILog> = ({
)}
{expanded && jsonData && <Pre block>{jsonData}</Pre>}
</LogText>
<br />
</>
)
}

View File

@@ -101,7 +101,7 @@ const Navigation = () => {
<Spinner />
) : (
<>
<Heading css={{ lineHeight: 1 }}>{snap.gistName || 'XRPL Hooks'}</Heading>
<Heading css={{ lineHeight: 1 }}>{snap.gistName || 'Xahau Hooks'}</Heading>
<Text css={{ fontSize: '$xs', color: '$mauve10', lineHeight: 1 }}>
{snap.files.length > 0 ? 'Gist: ' : 'Builder'}
{snap.files.length > 0 && (
@@ -180,7 +180,7 @@ const Navigation = () => {
fontWeight: '$bold'
}}
>
<Logo width="48px" height="48px" /> XRPL Hooks Builder
<Logo width="48px" height="48px" /> Xahau Hooks Builder
</DialogTitle>
<DialogDescription as="div">
<Text
@@ -191,7 +191,7 @@ const Navigation = () => {
mb: '$7'
}}
>
Hooks add smart contract functionality to the XRP Ledger.
Hooks add smart contract functionality to the Xahau Network.
</Text>
<Flex css={{ flexDirection: 'column', gap: '$2', mt: '$2' }}>
<Text
@@ -210,9 +210,9 @@ const Navigation = () => {
as="a"
rel="noreferrer noopener"
target="_blank"
href="https://github.com/XRPL-Labs/xrpld-hooks"
href="https://github.com/Xahau"
>
<ArrowUpRight size="15px" /> Hooks Github
<ArrowUpRight size="15px" /> Xahau Github
</Text>
<Text
@@ -231,7 +231,7 @@ const Navigation = () => {
as="a"
rel="noreferrer noopener"
target="_blank"
href="https://xrpl-hooks.readme.io/v2.0/docs"
href="https://docs.xahau.network/readme-1"
>
<ArrowUpRight size="15px" /> Hooks documentation
</Text>
@@ -352,7 +352,7 @@ const Navigation = () => {
</Button>
</Link>
</ButtonGroup>
<Link href="https://xrpl-hooks.readme.io/v2.0" passHref>
<Link href="https://xrpl-hooks.readme.io/" passHref>
<a target="_blank" rel="noreferrer noopener">
<Button outline>
<BookOpen size="15px" />

24
components/ResultLink.tsx Normal file
View File

@@ -0,0 +1,24 @@
import { FC } from 'react'
import { Link } from '.'
interface Props {
result?: string
}
const ResultLink: FC<Props> = ({ result }) => {
if (!result) return null
let href: string
if (result === 'tesSUCCESS') {
href = 'https://xrpl.org/tes-success.html'
} else {
// Going shortcut here because of url structure, if that changes we will do it manually
href = `https://xrpl.org/${result.slice(0, 3)}-codes.html`
}
return (
<Link as="a" href={href} target="_blank" rel="noopener noreferrer">
{result}
</Link>
)
}
export default ResultLink

View File

@@ -21,7 +21,7 @@ import { saveFile } from '../../state/actions/saveFile'
import { getErrors, getTags } from '../../utils/comment-parser'
import toast from 'react-hot-toast'
const generateHtmlTemplate = (code: string, data?: Record<string, any>) => {
const generateHtmlTemplate = async (code: string, data?: Record<string, any>) => {
let processString: string | undefined
const process = { env: { NODE_ENV: 'production' } } as any
if (data) {
@@ -29,8 +29,10 @@ const generateHtmlTemplate = (code: string, data?: Record<string, any>) => {
process.env[key] = data[key]
})
}
processString = JSON.stringify(process)
const libs = (await import("xrpl-accountlib/dist/browser.hook-bundle.js")).default;
return `
<html>
<head>
@@ -72,7 +74,9 @@ const generateHtmlTemplate = (code: string, data?: Record<string, any>) => {
window.addEventListener('error', windowErrorHandler);
</script>
<script>
${libs}
</script>
<script type="module">
${code}
</script>
@@ -100,6 +104,7 @@ const RunScript: React.FC<{ file: IFile }> = ({ file: { content, name } }) => {
const [fields, setFields] = useState<Fields>({})
const [iFrameCode, setIframeCode] = useState('')
const [isDialogOpen, setIsDialogOpen] = useState(false)
const [isLoading, setIsLoading] = useState(false)
const getFields = useCallback(() => {
const inputTags = ['input', 'param', 'arg', 'argument']
@@ -127,16 +132,31 @@ const RunScript: React.FC<{ file: IFile }> = ({ file: { content, name } }) => {
return fields
}, [content])
const runScript = useCallback(() => {
const runScript = useCallback(async () => {
setIsLoading(true);
try {
// Show loading toast only after 1 second, otherwise skip it.
let loaded = false
let toastId: string | undefined;
setTimeout(() => {
if (!loaded) {
toastId = toast.loading('Loading packages, may take a few seconds...', {
position: 'bottom-center',
})
}
}, 1000)
let data: any = {}
Object.keys(fields).forEach(key => {
data[key] = fields[key].value
})
const template = generateHtmlTemplate(content, data)
const template = await generateHtmlTemplate(content, data)
setIframeCode(template)
loaded = true
if (toastId) {
toast.dismiss(toastId)
}
state.scriptLogs = [{ type: 'success', message: 'Started running...' }]
} catch (err) {
state.scriptLogs = [
@@ -145,6 +165,7 @@ const RunScript: React.FC<{ file: IFile }> = ({ file: { content, name } }) => {
{ type: 'error', message: err?.message || 'Could not parse template' }
]
}
setIsLoading(false);
}, [content, fields, snap.scriptLogs])
useEffect(() => {
@@ -174,11 +195,11 @@ const RunScript: React.FC<{ file: IFile }> = ({ file: { content, name } }) => {
const isDisabled = Object.values(fields).some(field => field.required && !field.value)
const handleRun = useCallback(() => {
const handleRun = useCallback(async () => {
if (isDisabled) return toast.error('Please fill in all the required fields.')
state.scriptLogs = []
runScript()
await runScript();
setIsDialogOpen(false)
}, [isDisabled, runScript])
@@ -279,7 +300,7 @@ const RunScript: React.FC<{ file: IFile }> = ({ file: { content, name } }) => {
<DialogClose asChild>
<Button outline>Cancel</Button>
</DialogClose>
<Button variant="primary" isDisabled={isDisabled} onClick={handleRun}>
<Button variant="primary" isDisabled={isDisabled || isLoading} isLoading={isLoading} onClick={handleRun}>
Run script
</Button>
</Flex>

View File

@@ -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,93 +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'
}
},
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, {})

71
components/Sequence.tsx Normal file
View File

@@ -0,0 +1,71 @@
import { FC, useCallback, useState } from 'react'
import state from '../state'
import { Flex, Input, Button } from '.'
import fetchAccountInfo from '../utils/accountInfo'
import { useSnapshot } from 'valtio'
interface AccountSequenceProps {
address?: string
}
const AccountSequence: FC<AccountSequenceProps> = ({ address }) => {
const { accounts } = useSnapshot(state)
const account = accounts.find(acc => acc.address === address)
const [isLoading, setIsLoading] = useState(false)
const setSequence = useCallback(
(sequence: number) => {
const acc = state.accounts.find(acc => acc.address == address)
if (!acc) return
acc.sequence = sequence
},
[address]
)
const handleUpdateSequence = useCallback(
async (silent?: boolean) => {
if (!account) return
setIsLoading(true)
const info = await fetchAccountInfo(account.address, { silent })
if (info) {
setSequence(info.Sequence)
}
setIsLoading(false)
},
[account, setSequence]
)
const disabled = !account
return (
<Flex row align="center" fluid>
<Input
placeholder="Account sequence"
value={account?.sequence || ""}
disabled={!account}
type="number"
readOnly={true}
/>
<Button
size="xs"
variant="primary"
type="button"
outline
disabled={disabled}
isDisabled={disabled}
isLoading={isLoading}
css={{
background: '$backgroundAlt',
position: 'absolute',
right: '$2',
fontSize: '$xs',
cursor: 'pointer',
alignContent: 'center',
display: 'flex'
}}
onClick={() => handleUpdateSequence()}
>
Update
</Button>
</Flex>
)
}
export default AccountSequence

View File

@@ -21,6 +21,7 @@ import { prepareDeployHookTx, sha256 } from '../state/actions/deployHook'
import estimateFee from '../utils/estimateFee'
import { getParameters, getInvokeOptions, transactionOptions, SetHookData } from '../utils/setHook'
import { capitalize } from '../utils/helpers'
import AccountSequence from './Sequence'
export const SetHookDialog: React.FC<{ accountAddress: string }> = React.memo(
({ accountAddress }) => {
@@ -190,6 +191,10 @@ export const SetHookDialog: React.FC<{ accountAddress: string }> = React.memo(
onChange={(acc: any) => setSelectedAccount(acc)}
/>
</Box>
<Box css={{ width: '100%', position: 'relative' }}>
<Label>Sequence</Label>
<AccountSequence address={selectedAccount?.value} />
</Box>
<Box css={{ width: '100%' }}>
<Label>Invoke on transactions</Label>
<Controller

View File

@@ -72,7 +72,12 @@ const Tooltip: React.FC<React.ComponentProps<typeof StyledContent> & ITooltip> =
...rest
}) => {
return (
<TooltipPrimitive.Root open={open} defaultOpen={defaultOpen} onOpenChange={onOpenChange}>
<TooltipPrimitive.Root
open={open}
defaultOpen={defaultOpen}
onOpenChange={onOpenChange}
delayDuration={100}
>
<TooltipPrimitive.Trigger asChild>{children}</TooltipPrimitive.Trigger>
<StyledContent side="bottom" align="center" {...rest}>
<div dangerouslySetInnerHTML={{ __html: content }} />

View File

@@ -19,6 +19,8 @@ import { TxJson } from './json'
import { TxUI } from './ui'
import { default as _estimateFee } from '../../utils/estimateFee'
import toast from 'react-hot-toast'
import { combineFlags, extractFlags, transactionFlags } from '../../state/constants/flags'
import { SetHookData, toHex } from '../../utils/setHook'
export interface TransactionProps {
header: string
@@ -39,17 +41,40 @@ const Transaction: FC<TransactionProps> = ({ header, state: txState, ...props })
const prepareOptions = useCallback(
(state: Partial<TransactionState> = txState) => {
const { selectedTransaction, selectedDestAccount, selectedAccount, txFields } = state
const {
selectedTransaction,
selectedAccount,
txFields,
selectedFlags,
hookParameters,
memos
} = state
const TransactionType = selectedTransaction?.value || null
const Destination = selectedDestAccount?.value || txFields?.Destination
const Account = selectedAccount?.value || null
const Flags = combineFlags(selectedFlags?.map(flag => flag.value)) || txFields?.Flags
const HookParameters = Object.entries(hookParameters || {}).reduce<
SetHookData['HookParameters']
>((acc, [_, { label, value }]) => {
return acc.concat({
HookParameter: { HookParameterName: toHex(label), HookParameterValue: value }
})
}, [])
const Memos = memos
? Object.entries(memos).reduce<SetHookData['Memos']>((acc, [_, { format, data, type }]) => {
return acc?.concat({
Memo: { MemoData: data, MemoFormat: toHex(format), MemoType: toHex(type) }
})
}, [])
: undefined
return prepareTransaction({
...txFields,
HookParameters,
Flags,
TransactionType,
Destination,
Account
Account,
Memos
})
},
[txState]
@@ -65,15 +90,29 @@ const Transaction: FC<TransactionProps> = ({ header, state: txState, ...props })
}
}, [selectedAccount?.value, selectedTransaction?.value, setState, txIsLoading])
const getJsonString = useCallback(
(state?: Partial<TransactionState>) =>
JSON.stringify(prepareOptions?.(state) || {}, null, editorSettings.tabSize),
[editorSettings.tabSize, prepareOptions]
)
const saveEditorState = useCallback(
(value: string = '', transactionType?: string) => {
const pTx = prepareState(value, transactionType)
if (pTx) {
pTx.editorValue = getJsonString(pTx)
return setState(pTx)
}
},
[getJsonString, setState]
)
const submitTest = useCallback(async () => {
let st: TransactionState | undefined
const tt = txState.selectedTransaction?.value
if (viewType === 'json') {
// save the editor state first
const pst = prepareState(editorValue || '', tt)
if (!pst) return
st = setState(pst)
st = saveEditorState(editorValue, tt)
if (!st) return
}
const account = accounts.find(acc => acc.address === selectedAccount?.value)
@@ -86,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) {
@@ -104,23 +144,18 @@ const Transaction: FC<TransactionProps> = ({ header, state: txState, ...props })
}
setState({ txIsLoading: false })
}, [
txState.selectedTransaction?.value,
viewType,
accounts,
txIsDisabled,
setState,
header,
saveEditorState,
editorValue,
txState,
selectedAccount?.value,
prepareOptions
])
const getJsonString = useCallback(
(state?: Partial<TransactionState>) =>
JSON.stringify(prepareOptions?.(state) || {}, null, editorSettings.tabSize),
[editorSettings.tabSize, prepareOptions]
)
const resetState = useCallback(
(transactionType: SelectOption | undefined = defaultTransactionType) => {
const fields = getTxFields(transactionType?.value)
@@ -130,14 +165,12 @@ 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
}
nwState.txFields = fields
nwState.txFields = fields
const state = modifyTxState(header, nwState, { replaceState: true })
const editorValue = getJsonString(state)
return setState({ editorValue })
@@ -168,18 +201,34 @@ const Transaction: FC<TransactionProps> = ({ header, state: txState, ...props })
[accounts, prepareOptions, setState, txState]
)
const switchToJson = useCallback(() => {
const editorValue = getJsonString()
setState({ viewType: 'json', editorValue })
}, [getJsonString, setState])
const switchToUI = useCallback(() => {
setState({ viewType: 'ui' })
}, [setState])
return (
<Box css={{ position: 'relative', height: 'calc(100% - 28px)' }} {...props}>
{viewType === 'json' ? (
<TxJson
getJsonString={getJsonString}
saveEditorState={saveEditorState}
header={header}
state={txState}
setState={setState}
estimateFee={estimateFee}
/>
) : (
<TxUI state={txState} setState={setState} estimateFee={estimateFee} />
<TxUI
switchToJson={switchToJson}
state={txState}
resetState={resetState}
setState={setState}
estimateFee={estimateFee}
/>
)}
<Flex
row
@@ -195,8 +244,8 @@ const Transaction: FC<TransactionProps> = ({ header, state: txState, ...props })
<Button
onClick={() => {
if (viewType === 'ui') {
setState({ viewType: 'json' })
} else setState({ viewType: 'ui' })
switchToJson()
} else switchToUI()
}}
outline
>

View File

@@ -1,6 +1,6 @@
import { FC, useCallback, useEffect, useMemo, useState } from 'react'
import { FC, useCallback, useEffect, useState } from 'react'
import { useSnapshot } from 'valtio'
import state, { prepareState, transactionsData, TransactionState } from '../../state'
import state, { transactionsData, TransactionState } from '../../state'
import Text from '../Text'
import { Flex, Link } from '..'
import { showAlert } from '../../state/actions/showAlert'
@@ -11,27 +11,27 @@ import Monaco from '../Monaco'
import type monaco from 'monaco-editor'
interface JsonProps {
getJsonString?: (state?: Partial<TransactionState>) => string
getJsonString: (st?: Partial<TransactionState>) => string
saveEditorState: (val?: string, tt?: string) => TransactionState | undefined
header?: string
setState: (pTx?: Partial<TransactionState> | undefined) => void
state: TransactionState
estimateFee?: () => Promise<string | undefined>
}
export const TxJson: FC<JsonProps> = ({ getJsonString, state: txState, header, setState }) => {
export const TxJson: FC<JsonProps> = ({
getJsonString,
state: txState,
header,
setState,
saveEditorState
}) => {
const { editorSettings, accounts } = useSnapshot(state)
const { editorValue, estimatedFee } = txState
const { editorValue, estimatedFee, editorIsSaved } = txState
const [currTxType, setCurrTxType] = useState<string | undefined>(
txState.selectedTransaction?.value
)
useEffect(() => {
setState({
editorValue: getJsonString?.()
})
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [])
useEffect(() => {
const parsed = parseJSON(editorValue)
if (!parsed) return
@@ -44,29 +44,19 @@ export const TxJson: FC<JsonProps> = ({ getJsonString, state: txState, header, s
}
}, [editorValue])
const saveState = (value: string, transactionType?: string) => {
const tx = prepareState(value, transactionType)
if (tx) {
setState(tx)
setState({
editorValue: getJsonString?.(tx)
})
}
}
const discardChanges = () => {
showAlert('Confirm', {
body: 'Are you sure to discard these changes?',
confirmText: 'Yes',
onCancel: () => {},
onConfirm: () => setState({ editorValue: getJsonString?.() })
onConfirm: () => setState({ editorValue: getJsonString() })
})
}
const onExit = (value: string) => {
const options = parseJSON(value)
if (options) {
saveState(value, currTxType)
saveEditorState(value, currTxType)
return
}
showAlert('Error!', {
@@ -163,8 +153,6 @@ export const TxJson: FC<JsonProps> = ({ getJsonString, state: txState, header, s
})
}, [getSchemas, monacoInst])
const hasUnsaved = useMemo(() => editorValue !== getJsonString?.(), [editorValue, getJsonString])
return (
<Monaco
rootProps={{
@@ -174,7 +162,7 @@ export const TxJson: FC<JsonProps> = ({ getJsonString, state: txState, header, s
id={header}
height="100%"
value={editorValue}
onChange={val => setState({ editorValue: val })}
onChange={val => setState({ editorValue: val, editorIsSaved: false })}
onMount={(editor, monaco) => {
editor.updateOptions({
minimap: { enabled: false },
@@ -190,12 +178,12 @@ export const TxJson: FC<JsonProps> = ({ getJsonString, state: txState, header, s
model?.onWillDispose(() => onExit(model.getValue()))
}}
overlay={
hasUnsaved ? (
!editorIsSaved ? (
<Flex row align="center" css={{ fontSize: '$xs', color: '$textMuted', ml: 'auto' }}>
<Text muted small>
This file has unsaved changes.
</Text>
<Link css={{ ml: '$1' }} onClick={() => saveState(editorValue || '', currTxType)}>
<Link css={{ ml: '$1' }} onClick={() => saveEditorState(editorValue, currTxType)}>
save
</Link>
<Link css={{ ml: '$1' }} onClick={discardChanges}>

View File

@@ -1,62 +1,59 @@
import { FC, 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
resetState: (tt?: SelectOption) => TransactionState | undefined
state: TransactionState
estimateFee?: (...arg: any) => Promise<string | undefined>
switchToJson: () => void
}
export const TxUI: FC<UIProps> = ({ state: txState, setState, estimateFee }) => {
export const TxUI: FC<UIProps> = ({
state: txState,
setState,
resetState,
estimateFee,
switchToJson
}) => {
const { accounts } = useSnapshot(state)
const { selectedAccount, selectedDestAccount, selectedTransaction, txFields } = 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]) => ({
label,
value
}))
const [feeLoading, setFeeLoading] = useState(false)
const resetFields = useCallback(
(tt: string) => {
const fields = getTxFields(tt)
if (fields.Destination !== undefined) {
setState({ selectedDestAccount: null })
fields.Destination = ''
} else {
fields.Destination = undefined
}
return setState({ txFields: fields })
},
[setState]
)
const handleSetAccount = (acc: SelectOption) => {
setState({ selectedAccount: acc })
streamState.selectedAccount = acc
@@ -76,6 +73,22 @@ export const TxUI: FC<UIProps> = ({ state: txState, setState, estimateFee }) =>
[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)
@@ -92,15 +105,13 @@ export const TxUI: FC<UIProps> = ({ state: txState, setState, estimateFee }) =>
(tt: SelectOption) => {
setState({ selectedTransaction: tt })
const newState = resetFields(tt.value)
const newState = resetState(tt)
handleEstimateFee(newState, true)
},
[handleEstimateFee, resetFields, setState]
[handleEstimateFee, resetState, setState]
)
const switchToJson = () => setState({ viewType: 'json' })
// default tx
useEffect(() => {
if (selectedTransaction?.value) return
@@ -110,20 +121,23 @@ export const TxUI: FC<UIProps> = ({ state: txState, setState, estimateFee }) =>
}
}, [handleChangeTxType, selectedTransaction?.value])
const fields = useMemo(
() => getTxFields(selectedTransaction?.value),
[selectedTransaction?.value]
)
const richFields = ['TransactionType', 'Account', 'HookParameters', 'Memos']
const specialFields = ['TransactionType', 'Account']
if (fields.Destination !== undefined) {
specialFields.push('Destination')
if (flagsOptions.length) {
richFields.push('Flags')
}
const otherFields = Object.keys(txFields).filter(k => !specialFields.includes(k)) as [
keyof TxFields
]
const otherFields = Object.keys(txFields).filter(k => !richFields.includes(k)) as [keyof TxFields]
const amountOptions = [
{ label: 'XAH', value: 'xah' },
{ label: 'Token', value: 'token' }
] as const
const defaultTokenAmount = {
value: '0',
currency: '',
issuer: ''
}
return (
<Container
css={{
@@ -133,184 +147,437 @@ export const TxUI: FC<UIProps> = ({ state: txState, setState, estimateFee }) =>
}}
>
<Flex column fluid css={{ height: '100%', overflowY: 'auto', pr: '$1' }}>
<Flex
row
fluid
css={{
justifyContent: 'flex-end',
alignItems: 'center',
mb: '$3',
mt: '1px',
pr: '1px'
}}
>
<Text muted css={{ mr: '$3' }}>
Transaction type:{' '}
</Text>
<TxField label="Transaction type">
<Select
instanceId="transactionsType"
placeholder="Select transaction type"
options={transactionsOptions}
hideSelectedOptions
css={{ width: '70%' }}
value={selectedTransaction}
onChange={(tt: any) => handleChangeTxType(tt)}
/>
</Flex>
<Flex
row
fluid
css={{
justifyContent: 'flex-end',
alignItems: 'center',
mb: '$3',
pr: '1px'
}}
>
<Text muted css={{ mr: '$3' }}>
Account:{' '}
</Text>
</TxField>
<TxField label="Account">
<Select
instanceId="from-account"
placeholder="Select your account"
css={{ width: '70%' }}
options={accountOptions}
value={selectedAccount}
onChange={(acc: any) => handleSetAccount(acc)} // TODO make react-select have correct types for acc
/>
</Flex>
{fields.Destination !== undefined && (
<Flex
row
fluid
css={{
justifyContent: 'flex-end',
alignItems: 'center',
mb: '$3',
pr: '1px'
}}
>
<Text muted css={{ mr: '$3' }}>
Destination account:{' '}
</Text>
</TxField>
<TxField label="Sequence">
<AccountSequence address={selectedAccount?.value} />
</TxField>
{richFields.includes('Flags') && (
<TxField label="Flags">
<Select
instanceId="to-account"
placeholder="Select the destination account"
css={{ width: '70%' }}
options={destAccountOptions}
value={selectedDestAccount}
isClearable
onChange={(acc: any) => setState({ selectedDestAccount: acc })}
instanceId="flags"
placeholder="Select flags to apply"
menuPosition="fixed"
value={selectedFlags}
isMulti
options={flagsOptions}
onChange={flags => setState({ selectedFlags: flags as any })}
closeMenuOnSelect={
selectedFlags ? selectedFlags.length >= flagsOptions.length - 1 : false
}
/>
</Flex>
</TxField>
)}
{otherFields.map(field => {
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
return (
<Flex column key={field} css={{ mb: '$2', pr: '1px' }}>
<Flex
row
fluid
css={{
justifyContent: 'flex-end',
alignItems: 'center',
position: 'relative'
}}
>
<Text muted css={{ mr: '$3' }}>
{field + (isXrp ? ' (XRP)' : '')}:{' '}
</Text>
{isJson ? (
<Textarea
rows={rows}
value={value}
spellCheck={false}
onChange={switchToJson}
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={{
width: '70%',
flex: 'inherit',
resize: 'vertical'
ml: '$2',
width: '150px'
}}
/>
) : (
<Input
type={isFee ? 'number' : 'text'}
value={value}
onChange={e => {
if (isFee) {
const val = e.target.value.replaceAll('.', '').replaceAll(',', '')
handleSetField(field, val)
} else {
handleSetField(field, e.target.value)
}
}}
onKeyPress={
isFee
? e => {
if (e.key === '.' || e.key === ',') {
e.preventDefault()
}
}
: undefined
}
css={{
width: '70%',
flex: 'inherit',
'-moz-appearance': 'textfield',
'&::-webkit-outer-spin-button': {
'-webkit-appearance': 'none',
margin: 0
},
'&::-webkit-inner-spin-button ': {
'-webkit-appearance': 'none',
margin: 0
}
}}
/>
)}
{isFee && (
<Button
size="xs"
variant="primary"
outline
disabled={txState.txIsDisabled}
isDisabled={txState.txIsDisabled}
isLoading={feeLoading}
css={{
position: 'absolute',
right: '$2',
fontSize: '$xs',
cursor: 'pointer',
alignContent: 'center',
display: 'flex'
}}
onClick={() => handleEstimateFee()}
>
Suggest
</Button>
)}
</Flex>
</Flex>
<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 === 'xah') {
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}>
{isJson ? (
<Textarea
rows={rows}
value={value}
spellCheck={false}
onChange={switchToJson}
css={{
flex: 'inherit',
resize: 'vertical'
}}
/>
) : (
<Input
type={isFee ? 'number' : 'text'}
value={value}
onChange={e => {
if (isFee) {
const val = e.target.value.replaceAll('.', '').replaceAll(',', '')
handleSetField(field, val)
} else {
handleSetField(field, e.target.value)
}
}}
onKeyPress={
isFee
? e => {
if (e.key === '.' || e.key === ',') {
e.preventDefault()
}
}
: undefined
}
css={{
flex: 'inherit',
'-moz-appearance': 'textfield',
'&::-webkit-outer-spin-button': {
'-webkit-appearance': 'none',
margin: 0
},
'&::-webkit-inner-spin-button ': {
'-webkit-appearance': 'none',
margin: 0
}
}}
/>
)}
{isFee && (
<Button
size="xs"
variant="primary"
outline
disabled={txState.txIsDisabled}
isDisabled={txState.txIsDisabled}
isLoading={feeLoading}
css={{
position: 'absolute',
right: '$2',
fontSize: '$xs',
cursor: 'pointer',
alignContent: 'center',
display: 'flex'
}}
onClick={() => handleEstimateFee()}
>
Suggest
</Button>
)}
</TxField>
)
})}
<TxField multiLine label="Hook parameters">
<Flex column fluid>
{Object.entries(hookParameters).map(([id, { label, value }]) => (
<Flex column key={id} css={{ mb: '$2' }}>
<Flex row>
<Input
placeholder="Parameter name"
value={label}
onChange={e => {
setState({
hookParameters: {
...hookParameters,
[id]: { label: e.target.value, value }
}
})
}}
/>
<Input
css={{ mx: '$2' }}
placeholder="Value (hex-quoted)"
value={value}
onChange={e => {
setState({
hookParameters: {
...hookParameters,
[id]: { label, value: e.target.value }
}
})
}}
/>
<Button
onClick={() => {
const { [id]: _, ...rest } = hookParameters
setState({ hookParameters: rest })
}}
variant="destroy"
>
<Trash weight="regular" size="16px" />
</Button>
</Flex>
</Flex>
))}
<Button
outline
fullWidth
type="button"
onClick={() => {
const id = Object.keys(hookParameters).length
setState({
hookParameters: { ...hookParameters, [id]: { label: '', value: '' } }
})
}}
>
<Plus size="16px" />
Add Hook Parameter
</Button>
</Flex>
</TxField>
<TxField multiLine label="Memos">
<Flex column fluid>
{Object.entries(memos).map(([id, memo]) => (
<Flex column key={id} css={{ mb: '$2' }}>
<Flex
row
css={{
flexWrap: 'wrap',
width: '100%'
}}
>
<Input
placeholder="Memo type"
value={memo.type}
onChange={e => {
setState({
memos: {
...memos,
[id]: { ...memo, type: e.target.value }
}
})
}}
/>
<Input
placeholder="Data (hex-quoted)"
css={{ mx: '$2' }}
value={memo.data}
onChange={e => {
setState({
memos: {
...memos,
[id]: { ...memo, data: e.target.value }
}
})
}}
/>
<Input
placeholder="Format"
value={memo.format}
onChange={e => {
setState({
memos: {
...memos,
[id]: { ...memo, format: e.target.value }
}
})
}}
/>
<Button
css={{ ml: '$2' }}
onClick={() => {
const { [id]: _, ...rest } = memos
setState({ memos: rest })
}}
variant="destroy"
>
<Trash weight="regular" size="16px" />
</Button>
</Flex>
</Flex>
))}
<Button
outline
fullWidth
type="button"
onClick={() => {
const id = Object.keys(memos).length
setState({
memos: { ...memos, [id]: { data: '', format: '', type: '' } }
})
}}
>
<Plus size="16px" />
Add Memo
</Button>
</Flex>
</TxField>
</Flex>
</Container>
)
}
export const CreatableAccount: FC<{
value: string | undefined
field: keyof TxFields
placeholder?: string
setField: (field: keyof TxFields, value: string, opFields?: TxFields) => void
}> = ({ value, field, setField, placeholder }) => {
const { accounts } = useSnapshot(state)
const accountOptions: SelectOption[] = accounts.map(acc => ({
label: acc.name,
value: acc.address
}))
const label = accountOptions.find(a => a.value === value)?.label || value
const val = {
value,
label
}
placeholder = placeholder || `${capitalize(field)} account`
return (
<CreatableSelect
isClearable
instanceId={field}
placeholder={placeholder}
options={accountOptions}
value={value ? val : undefined}
onChange={(acc: any) => setField(field, acc?.value)}
/>
)
}
export const TxField: FC<{ label: string; children: ReactNode; multiLine?: boolean }> = ({
label,
children,
multiLine = false
}) => {
return (
<Flex
row
fluid
css={{
justifyContent: 'flex-end',
alignItems: multiLine ? 'flex-start' : 'center',
position: 'relative',
mb: '$2',
mt: '1px',
pr: '1px'
}}
>
<Text muted css={{ mr: '$3', mt: multiLine ? '$2' : 0 }}>
{label}:{' '}
</Text>
<Flex css={{ width: '70%', alignItems: 'center' }}>{children}</Flex>
</Flex>
)
}

View File

@@ -1,7 +1,7 @@
{
"uri": "file:///amount-schema.json",
"title": "Amount",
"description": "Specify xrp in drops and tokens as objects.",
"description": "Specify XAH in drops and tokens as objects.",
"schema": {
"anyOf": [
{
@@ -13,7 +13,7 @@
"type": "object",
"properties": {
"currency": {
"description": "Arbitrary currency code for the token. Cannot be XRP."
"description": "Arbitrary currency code for the token. Cannot be XAH."
},
"value": {
"type": ["string", "number"],
@@ -28,7 +28,7 @@
],
"defaultSnippets": [
{
"label": "Xrp",
"label": "XAH",
"body": "1000000"
},
{

409
content/hook-set-codes.json Normal file
View File

@@ -0,0 +1,409 @@
[
{
"code": 1,
"identifier": "AMENDMENT_DISABLED",
"description": "attempt to HookSet when amendment is not yet enabled."
},
{
"code": 2,
"identifier": "API_ILLEGAL",
"description": "HookSet object contained HookApiVersion for existing HookDefinition"
},
{
"code": 3,
"identifier": "API_INVALID",
"description": "HookSet object contained HookApiVersion for unrecognised hook API "
},
{
"code": 4,
"identifier": "API_MISSING",
"description": "HookSet object lacked HookApiVersion"
},
{
"code": 5,
"identifier": "BLOCK_ILLEGAL",
"description": " a block end instruction moves execution below depth 0 {{}}`}` <= like this"
},
{
"code": 6,
"identifier": "CALL_ILLEGAL",
"description": "wasm tries to call a non-whitelisted function"
},
{
"code": 7,
"identifier": "CALL_INDIRECT",
"description": "wasm used call indirect instruction which is disallowed"
},
{
"code": 8,
"identifier": "CREATE_FLAG",
"description": "create operation requires hsoOVERRIDE"
},
{
"code": 9,
"identifier": "DELETE_FIELD",
"description": ""
},
{
"code": 10,
"identifier": "DELETE_FLAG",
"description": "delete operation requires hsoOVERRIDE"
},
{
"code": 11,
"identifier": "DELETE_NOTHING",
"description": "delete operation would delete nothing"
},
{
"code": 12,
"identifier": "EXPORTS_MISSING",
"description": "hook did not export *any* functions (should be cbak, hook)"
},
{
"code": 13,
"identifier": "EXPORT_CBAK_FUNC",
"description": "hook did not export correct func def int64_t cbak(uint32_t)"
},
{
"code": 14,
"identifier": "EXPORT_HOOK_FUNC",
"description": "hook did not export correct func def int64_t hook(uint32_t)"
},
{
"code": 15,
"identifier": "EXPORT_MISSING",
"description": "distinct from export*S*_missing, either hook or cbak is missing"
},
{
"identifier": "FLAGS_INVALID",
"code": 16,
"description": "HookSet flags were invalid for specified operation "
},
{
"identifier": "FUNCS_MISSING",
"code": 17,
"description": "hook did not include function code for any functions "
},
{
"identifier": "FUNC_PARAM_INVALID",
"code": 18,
"description": "parameter types may only be i32 i64 u32 u64 "
},
{
"identifier": "FUNC_RETURN_COUNT",
"code": 19,
"description": "a function type is defined in the wasm which returns > 1 return value "
},
{
"identifier": "FUNC_RETURN_INVALID",
"code": 20,
"description": "a function type does not return i32 i64 u32 or u64 "
},
{
"identifier": "FUNC_TYPELESS",
"code": 21,
"description": "hook defined hook/cbak but their type is not defined in wasm "
},
{
"identifier": "FUNC_TYPE_INVALID",
"code": 22,
"description": "malformed and illegal wasm in the func type section "
},
{
"identifier": "GRANTS_EMPTY",
"code": 23,
"description": "HookSet object contained an empty grants array (you should remove it) "
},
{
"identifier": "GRANTS_EXCESS",
"code": 24,
"description": "HookSet object cotnained a grants array with too many grants "
},
{
"identifier": "GRANTS_FIELD",
"code": 25,
"description": "HookSet object contained a grant without Authorize or HookHash "
},
{
"identifier": "GRANTS_ILLEGAL",
"code": 26,
"description": "Hookset object contained grants array which contained a non Grant object "
},
{
"identifier": "GUARD_IMPORT",
"code": 27,
"description": "guard import is missing "
},
{
"identifier": "GUARD_MISSING",
"code": 28,
"description": "guard call missing at top of loop "
},
{
"identifier": "GUARD_PARAMETERS",
"code": 29,
"description": "guard called but did not use constant expressions for params "
},
{
"identifier": "HASH_OR_CODE",
"code": 30,
"description": "HookSet object can contain only one of CreateCode and HookHash "
},
{
"identifier": "HOOKON_MISSING",
"code": 31,
"description": "HookSet object did not contain HookOn but should have "
},
{
"identifier": "HOOKS_ARRAY_BAD",
"code": 32,
"description": "attempt to HookSet with a Hooks array containing a non-Hook obj "
},
{
"identifier": "HOOKS_ARRAY_BLANK",
"code": 33,
"description": "all hook set objs were blank "
},
{
"identifier": "HOOKS_ARRAY_EMPTY",
"code": 34,
"description": "attempt to HookSet with an empty Hooks array "
},
{
"identifier": "HOOKS_ARRAY_MISSING",
"code": 35,
"description": "attempt to HookSet without a Hooks array "
},
{
"identifier": "HOOKS_ARRAY_TOO_BIG",
"code": 36,
"description": "attempt to HookSet with a Hooks array beyond the chain size limit "
},
{
"identifier": "HOOK_ADD",
"code": 37,
"description": "Informational: adding ltHook to directory "
},
{
"identifier": "HOOK_DEF_MISSING",
"code": 38,
"description": "attempt to reference a hook definition (by hash) that is not on ledger "
},
{
"identifier": "HOOK_DELETE",
"code": 39,
"description": "unable to delete ltHook from owner "
},
{
"identifier": "HOOK_INVALID_FIELD",
"code": 40,
"description": "HookSetObj contained an illegal/unexpected field "
},
{
"identifier": "HOOK_PARAMS_COUNT",
"code": 41,
"description": "hookset obj would create too many hook parameters "
},
{
"identifier": "HOOK_PARAM_SIZE",
"code": 42,
"description": "hookset obj sets a parameter or value that exceeds max allowable size "
},
{
"identifier": "IMPORTS_MISSING",
"code": 43,
"description": "hook must import guard, and accept/rollback "
},
{
"identifier": "IMPORT_ILLEGAL",
"code": 44,
"description": "attempted import of a non-whitelisted function "
},
{
"identifier": "IMPORT_MODULE_BAD",
"code": 45,
"description": "hook attempted to specify no or a bad import module "
},
{
"identifier": "IMPORT_MODULE_ENV",
"code": 46,
"description": "hook attempted to specify import module not named env "
},
{
"identifier": "IMPORT_NAME_BAD",
"code": 47,
"description": "import name was too short or too long "
},
{
"identifier": "INSTALL_FLAG",
"code": 48,
"description": "install operation requires hsoOVERRIDE "
},
{
"identifier": "INSTALL_MISSING",
"code": 49,
"description": "install operation specifies hookhash which doesn't exist on the ledger "
},
{
"identifier": "INSTRUCTION_COUNT",
"code": 50,
"description": "worst case execution instruction count as computed by HookSet "
},
{
"identifier": "INSTRUCTION_EXCESS",
"code": 51,
"description": "worst case execution instruction count was too large "
},
{
"identifier": "MEMORY_GROW",
"code": 52,
"description": "memory.grow instruction is present but disallowed "
},
{
"identifier": "NAMESPACE_MISSING",
"code": 53,
"description": "HookSet object lacked HookNamespace "
},
{
"identifier": "NSDELETE",
"code": 54,
"description": "Informational: a namespace is being deleted "
},
{
"identifier": "NSDELETE_ACCOUNT",
"code": 55,
"description": "nsdelete tried to delete ns from a non-existing account "
},
{
"identifier": "NSDELETE_COUNT",
"code": 56,
"description": "namespace state count less than 0 / overflow "
},
{
"identifier": "NSDELETE_DIR",
"code": 57,
"description": "could not delete directory node in ledger "
},
{
"identifier": "NSDELETE_DIRECTORY",
"code": 58,
"description": "nsdelete operation failed to delete ns directory "
},
{
"identifier": "NSDELETE_DIR_ENTRY",
"code": 59,
"description": "nsdelete operation failed due to bad entry in ns directory "
},
{
"identifier": "NSDELETE_ENTRY",
"code": 60,
"description": "nsdelete operation failed due to missing hook state entry "
},
{
"identifier": "NSDELETE_FIELD",
"code": 61
},
{
"identifier": "NSDELETE_FLAGS",
"code": 62
},
{
"identifier": "NSDELETE_NONSTATE",
"code": 63,
"description": "nsdelete operation failed due to the presence of a non-hookstate obj "
},
{
"identifier": "NSDELETE_NOTHING",
"code": 64,
"description": "hsfNSDELETE provided but nothing to delete "
},
{
"identifier": "OPERATION_INVALID",
"code": 65,
"description": "could not deduce an operation from the provided hookset obj "
},
{
"identifier": "OVERRIDE_MISSING",
"code": 66,
"description": "HookSet object was trying to update or delete a hook but lacked hsfOVERRIDE "
},
{
"identifier": "PARAMETERS_FIELD",
"code": 67,
"description": "HookParameters contained a HookParameter with an invalid key in it "
},
{
"identifier": "PARAMETERS_ILLEGAL",
"code": 68,
"description": "HookParameters contained something other than a HookParameter "
},
{
"identifier": "PARAMETERS_NAME",
"code": 69,
"description": "HookParameters contained a HookParameter which lacked ParameterName field "
},
{
"identifier": "PARAM_HOOK_CBAK",
"code": 70,
"description": "hook and cbak must take exactly one u32 parameter "
},
{
"identifier": "RETURN_HOOK_CBAK",
"code": 71,
"description": "hook and cbak must retunr i64 "
},
{
"identifier": "SHORT_HOOK",
"code": 72,
"description": "web assembly byte code ended abruptly "
},
{
"identifier": "TYPE_INVALID",
"code": 73,
"description": "malformed and illegal wasm specifying an illegal local var type "
},
{
"identifier": "WASM_BAD_MAGIC",
"code": 74,
"description": "wasm magic number missing or not wasm "
},
{
"identifier": "WASM_INVALID",
"code": 75,
"description": "set hook operation would set invalid wasm "
},
{
"identifier": "WASM_PARSE_LOOP",
"code": 76,
"description": "wasm section parsing resulted in an infinite loop "
},
{
"identifier": "WASM_SMOKE_TEST",
"code": 77,
"description": "Informational: first attempt to load wasm into wasm runtime "
},
{
"identifier": "WASM_TEST_FAILURE",
"code": 78,
"description": "the smoke test failed "
},
{
"identifier": "WASM_TOO_BIG",
"code": 79,
"description": "set hook would exceed maximum hook size "
},
{
"identifier": "WASM_TOO_SMALL",
"code": 80
},
{
"identifier": "WASM_VALIDATION",
"code": 81,
"description": "a generic error while parsing wasm, usually leb128 overflow"
},
{
"identifier": "HOOK_CBAK_DIFF_TYPES",
"code": 82,
"description": "hook and cbak function definitions were different"
}
]

View File

@@ -2,11 +2,14 @@
{
"TransactionType": "AccountDelete",
"Account": "rWYkbWkCeg8dP6rXALnjgZSjjLyih5NXm",
"Destination": "rPT1Sjq2YGrBMTttX4GZHjKu9dyfzbpAYe",
"Destination": {
"$type": "account",
"$value": ""
},
"DestinationTag": 13,
"Fee": "2000000",
"Sequence": 2470665,
"Flags": 2147483648
"Flags": "2147483648"
},
{
"TransactionType": "AccountSet",
@@ -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",
@@ -48,13 +58,16 @@
"Account": "rsUiUMpnrgxQp24dJYZDhmV4bE3aBtQyt8",
"Authorize": "rEhxGqkqPPSxQ3P25J66ft5TwpzV14k2de",
"Fee": "10",
"Flags": 2147483648,
"Flags": "2147483648",
"Sequence": 2
},
{
"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,60 +92,20 @@
{
"Account": "rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn",
"TransactionType": "EscrowFinish",
"Owner": "rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn",
"Owner": {
"$type": "account",
"$value": ""
},
"OfferSequence": 7,
"Condition": "A0258020E3B0C44298FC1C149AFBF4C8996FB92427AE41E4649B934CA495991B7852B855810100",
"Fulfillment": "A0028000",
"Fee": "10"
},
{
"TransactionType": "NFTokenMint",
"Account": "rvYAfWj5gh67oV6fW32ZzP3Aw4Eubs59B",
"Fee": "10",
"NFTokenTaxon": 0,
"URI": "697066733A2F2F516D614374444B5A4656767666756676626479346573745A626851483744586831364354707631686F776D424779"
},
{
"TransactionType": "NFTokenBurn",
"Account": "rvYAfWj5gh67oV6fW32ZzP3Aw4Eubs59B",
"Fee": "10",
"NFTokenID": "000B013A95F14B0044F78A264E41713C64B5F89242540EE208C3098E00000D65"
},
{
"TransactionType": "NFTokenAcceptOffer",
"Account": "rvYAfWj5gh67oV6fW32ZzP3Aw4Eubs59B",
"Fee": "10",
"NFTokenSellOffer": "A2FA1A9911FE2AEF83DAB05F437768E26A301EF899BD31EB85E704B3D528FF18",
"NFTokenBuyOffer": "4AAAEEA76E3C8148473CB3840CE637676E561FB02BD4CA22CA59729EA815B862",
"NFTokenBrokerFee": "10"
},
{
"TransactionType": "NFTokenCancelOffer",
"Account": "ra5nK24KXen9AHvsdFTKHSANinZseWnPcX",
"Fee": "10",
"NFTokenOffers": {
"$type": "json",
"$value": ["4AAAEEA76E3C8148473CB3840CE637676E561FB02BD4CA22CA59729EA815B862"]
}
},
{
"TransactionType": "NFTokenCreateOffer",
"Account": "rs8jBmmfpwgmrSPgwMsh7CvKRmRt1JTVSX",
"NFTokenID": "000100001E962F495F07A990F4ED55ACCFEEF365DBAA76B6A048C0A200000007",
"Amount": {
"$value": "100",
"$type": "xrp"
},
"Flags": 1,
"Destination": "",
"Fee": "10"
},
{
"TransactionType": "OfferCancel",
"Account": "ra5nK24KXen9AHvsdFTKHSANinZseWnPcX",
"Fee": "12",
"Flags": 0,
"LastLedgerSequence": 7108629,
"Flags": "0",
"OfferSequence": 6,
"Sequence": 7
},
@@ -137,25 +113,34 @@
"TransactionType": "OfferCreate",
"Account": "ra5nK24KXen9AHvsdFTKHSANinZseWnPcX",
"Fee": "12",
"Flags": 0,
"LastLedgerSequence": 7108682,
"Flags": "0",
"Sequence": 8,
"TakerGets": "6000000",
"Amount": {
"$value": "100",
"$type": "xrp"
"TakerGets": {
"$type": "amount.xrp",
"$value": "6000000"
},
"TakerPays": {
"$type": "amount.token",
"$value": {
"currency": "USD",
"issuer": "rhQEswwTsjMXk75QL9Dd9RWZAokNHTzJpr",
"value": "2"
}
}
},
{
"TransactionType": "Payment",
"Account": "rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn",
"Destination": "ra5nK24KXen9AHvsdFTKHSANinZseWnPcX",
"Destination": {
"$type": "account",
"$value": ""
},
"Amount": {
"$value": "100",
"$type": "xrp"
"$type": "amount.xrp"
},
"Fee": "12",
"Flags": 2147483648,
"Flags": "2147483648",
"Sequence": 2
},
{
@@ -163,9 +148,12 @@
"TransactionType": "PaymentChannelCreate",
"Amount": {
"$value": "100",
"$type": "xrp"
"$type": "amount.xrp"
},
"Destination": {
"$type": "account",
"$value": ""
},
"Destination": "rsA2LpzuawewSBQXkiju3YQTMzW13pAAdW",
"SettleDelay": 86400,
"PublicKey": "32D2471DB72B27E3310F355BB33E339BF26F8392D5A93D3BC0FC3B566612DA0F0A",
"CancelAfter": 533171558,
@@ -179,20 +167,20 @@
"Channel": "C1AE6DDDEEC05CF2978C0BAD6FE302948E9533691DC749DCDD3B9E5992CA6198",
"Amount": {
"$value": "200",
"$type": "xrp"
"$type": "amount.xrp"
},
"Expiration": 543171558,
"Fee": "10"
},
{
"Flags": 0,
"Flags": "0",
"TransactionType": "SetRegularKey",
"Account": "rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn",
"Fee": "12",
"RegularKey": "rAR8rR8sUkBoCZFawhkWzY4Y5YoyuznwD"
},
{
"Flags": 0,
"Flags": "0",
"TransactionType": "SignerListSet",
"Account": "rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn",
"Fee": "12",
@@ -232,16 +220,67 @@
"TransactionType": "TrustSet",
"Account": "ra5nK24KXen9AHvsdFTKHSANinZseWnPcX",
"Fee": "12",
"Flags": 262144,
"LastLedgerSequence": 8007750,
"Flags": "262144",
"LimitAmount": {
"$type": "json",
"$type": "amount.token",
"$value": {
"currency": "USD",
"issuer": "rsP3mgGb2tcYUrxiLFiHJiQXhsziegtwBc",
"issuer": "rhQEswwTsjMXk75QL9Dd9RWZAokNHTzJpr",
"value": "100"
}
},
"Sequence": 12
},
{
"TransactionType": "Invoke",
"Destination": {
"$type": "account",
"$value": ""
},
"Fee": "12"
},
{
"TransactionType": "URITokenMint",
"Account": "ra5nK24KXen9AHvsdFTKHSANinZseWnPcX",
"URI": "697066733A2F2F434944",
"Fee": "10",
"Sequence": 1
},
{
"TransactionType": "URITokenBurn",
"Account": "ra5nK24KXen9AHvsdFTKHSANinZseWnPcX",
"URITokenID": "B792B56B558C89C4E942E41B5DB66074E005CB198DB70C26C707AAC2FF5F74CB",
"Fee": "10",
"Sequence": 1
},
{
"TransactionType": "URITokenCreateSellOffer",
"Account": "ra5nK24KXen9AHvsdFTKHSANinZseWnPcX",
"URITokenID": "B792B56B558C89C4E942E41B5DB66074E005CB198DB70C26C707AAC2FF5F74CB",
"Destination": "rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn",
"Amount": {
"$value": "100",
"$type": "amount.xrp"
},
"Fee": "10",
"Sequence": 1
},
{
"TransactionType": "URITokenCancelSellOffer",
"Account": "ra5nK24KXen9AHvsdFTKHSANinZseWnPcX",
"URITokenID": "B792B56B558C89C4E942E41B5DB66074E005CB198DB70C26C707AAC2FF5F74CB",
"Fee": "10",
"Sequence": 1
},
{
"TransactionType": "URITokenBuy",
"Account": "ra5nK24KXen9AHvsdFTKHSANinZseWnPcX",
"URITokenID": "B792B56B558C89C4E942E41B5DB66074E005CB198DB70C26C707AAC2FF5F74CB",
"Amount": {
"$value": "100",
"$type": "amount.xrp"
},
"Fee": "10",
"Sequence": 1
}
]
]

View File

@@ -12,7 +12,7 @@ module.exports = {
config.resolve.fallback.fs = false
}
config.module.rules.push({
test: /\.md$/,
test: [/\.md$/, /hook-bundle\.js$/],
use: 'raw-loader'
})
return config

View File

@@ -8,7 +8,8 @@
"start": "next start",
"lint": "next lint",
"format": "prettier --write .",
"postinstall": "patch-package"
"postinstall": "patch-package && yarn run postinstall-postinstall",
"postinstall-postinstall": "./node_modules/.bin/browserify -r ripple-binary-codec -r ripple-keypairs -r ripple-address-codec -r ripple-secret-codec -r ./node_modules/xrpl-accountlib/dist/index.js:xrpl-accountlib -o node_modules/xrpl-accountlib/dist/browser.hook-bundle.js"
},
"dependencies": {
"@codingame/monaco-jsonrpc": "^0.3.1",
@@ -37,7 +38,7 @@
"lodash.xor": "^4.5.0",
"monaco-editor": "^0.33.0",
"next": "^12.0.4",
"next-auth": "^4.10.3",
"next-auth": "^4.24.11",
"next-plausible": "^3.2.0",
"next-themes": "^0.1.1",
"normalize-url": "^7.0.2",
@@ -64,9 +65,9 @@
"valtio": "^1.2.5",
"vscode-languageserver": "^7.0.0",
"vscode-uri": "^3.0.2",
"wabt": "1.0.16",
"xrpl-accountlib": "^1.5.2",
"xrpl-client": "^1.9.4"
"wabt": "^1.0.30",
"xrpl-accountlib": "^1.6.1",
"xrpl-client": "^2.0.2"
},
"devDependencies": {
"@types/dinero.js": "^1.9.0",
@@ -75,12 +76,16 @@
"@types/lodash.xor": "^4.5.6",
"@types/pako": "^1.0.2",
"@types/react": "17.0.31",
"browserify": "^17.0.0",
"eslint": "7.32.0",
"eslint-config-next": "11.1.2",
"raw-loader": "^4.0.2",
"typescript": "4.4.4"
"typescript": "^4.9.5"
},
"resolutions": {
"ripple-binary-codec": "=1.4.2"
"ripple-binary-codec": "=1.6.0"
},
"engines": {
"node": ">=22.0.0"
}
}

View File

@@ -58,22 +58,22 @@ function MyApp({ Component, pageProps: { session, ...pageProps } }: AppProps) {
<meta name="format-detection" content="telephone=no" />
<meta property="og:url" content={`${origin}${router.asPath}`} />
<title>XRPL Hooks Builder</title>
<meta property="og:title" content="XRPL Hooks Builder" />
<meta name="twitter:title" content="XRPL Hooks Builder" />
<title>Xahau Hooks Builder</title>
<meta property="og:title" content="Xahau Hooks Builder" />
<meta name="twitter:title" content="Xahau Hooks Builder" />
<meta name="twitter:card" content="summary_large_image" />
<meta name="twitter:site" content="@XRPLF" />
<meta
name="description"
content="Hooks Builder, add smart contract functionality to the XRP Ledger."
content="Hooks Builder, add smart contract functionality to the Xahau Network."
/>
<meta
property="og:description"
content="Hooks Builder, add smart contract functionality to the XRP Ledger."
content="Hooks Builder, add smart contract functionality to the Xahau Network."
/>
<meta
name="twitter:description"
content="Hooks Builder, add smart contract functionality to the XRP Ledger."
content="Hooks Builder, add smart contract functionality to the Xahau Network."
/>
<meta property="og:image" content={`${origin}${shareImg}`} />
<meta property="og:image:width" content="1200" />
@@ -121,7 +121,7 @@ function MyApp({ Component, pageProps: { session, ...pageProps } }: AppProps) {
<Alert />
<Flex
as="a"
href="https://github.com/XRPLF/Hooks/discussions"
href="https://github.com/Xahau/xrpl-hooks-ide/issues"
target="_blank"
rel="noopener noreferrer"
css={{ position: 'fixed', right: '$4', bottom: '$4' }}

View File

@@ -1,5 +1,11 @@
import NextAuth from 'next-auth'
declare module "next-auth" {
interface User {
username: string
}
}
export default NextAuth({
// Configure one or more authentication providers
providers: [

View File

@@ -150,6 +150,7 @@ const Home: NextPage = () => {
const activeFile = snap.files[snap.active] as IFile | undefined
const activeFileExt = getFileExtention(activeFile?.name)
const canCompile = activeFileExt === 'c' || activeFileExt === 'wat'
return (
<Split
direction="vertical"
@@ -162,7 +163,7 @@ const Home: NextPage = () => {
>
<main style={{ display: 'flex', flex: 1, position: 'relative' }}>
<HooksEditor />
{activeFileExt === 'c' && (
{canCompile && (
<Hotkeys
keyName="command+b,ctrl+b"
onKeyDown={() => !snap.compiling && snap.files.length && compileCode(snap.active)}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,346 @@
diff --git a/node_modules/ripple-binary-codec/dist/enums/definitions.json b/node_modules/ripple-binary-codec/dist/enums/definitions.json
index e623376..7e1e4d5 100644
--- a/node_modules/ripple-binary-codec/dist/enums/definitions.json
+++ b/node_modules/ripple-binary-codec/dist/enums/definitions.json
@@ -44,11 +44,16 @@
"NegativeUNL": 78,
"NFTokenPage": 80,
"NFTokenOffer": 55,
+ "URIToken": 85,
"Any": -3,
"Child": -2,
"Nickname": 110,
"Contract": 99,
- "GeneratorMap": 103
+ "GeneratorMap": 103,
+ "Hook": 72,
+ "HookState": 118,
+ "HookDefinition": 68,
+ "EmittedTxn": 69
},
"FIELDS": [
[
@@ -321,6 +326,16 @@
"type": "UInt16"
}
],
+ [
+ "NetworkID",
+ {
+ "nth": 1,
+ "isVLEncoded": false,
+ "isSerialized": true,
+ "isSigningField": true,
+ "type": "UInt32"
+ }
+ ],
[
"Flags",
{
@@ -761,6 +776,36 @@
"type": "UInt32"
}
],
+ [
+ "LockCount",
+ {
+ "nth": 49,
+ "isVLEncoded": false,
+ "isSerialized": true,
+ "isSigningField": true,
+ "type": "UInt32"
+ }
+ ],
+ [
+ "FirstNFTokenSequence",
+ {
+ "nth": 50,
+ "isVLEncoded": false,
+ "isSerialized": true,
+ "isSigningField": true,
+ "type": "UInt32"
+ }
+ ],
+ [
+ "ImportSequence",
+ {
+ "nth": 97,
+ "isVLEncoded": false,
+ "isSerialized": true,
+ "isSigningField": true,
+ "type": "UInt32"
+ }
+ ],
[
"IndexNext",
{
@@ -891,16 +936,6 @@
"type": "UInt64"
}
],
- [
- "HookOn",
- {
- "nth": 16,
- "isVLEncoded": false,
- "isSerialized": true,
- "isSigningField": true,
- "type": "UInt64"
- }
- ],
[
"HookInstructionCount",
{
@@ -1151,6 +1186,16 @@
"type": "Hash256"
}
],
+ [
+ "HookOn",
+ {
+ "nth": 20,
+ "isVLEncoded": false,
+ "isSerialized": true,
+ "isSigningField": true,
+ "type": "Hash256"
+ }
+ ],
[
"Digest",
{
@@ -1281,6 +1326,36 @@
"type": "Hash256"
}
],
+ [
+ "OfferID",
+ {
+ "nth": 34,
+ "isVLEncoded": false,
+ "isSerialized": true,
+ "isSigningField": true,
+ "type": "Hash256"
+ }
+ ],
+ [
+ "EscrowID",
+ {
+ "nth": 35,
+ "isVLEncoded": false,
+ "isSerialized": true,
+ "isSigningField": true,
+ "type": "Hash256"
+ }
+ ],
+ [
+ "URITokenID",
+ {
+ "nth": 36,
+ "isVLEncoded": false,
+ "isSerialized": true,
+ "isSigningField": true,
+ "type": "Hash256"
+ }
+ ],
[
"Amount",
{
@@ -1421,6 +1496,56 @@
"type": "Amount"
}
],
+ [
+ "HookCallbackFee",
+ {
+ "nth": 20,
+ "isVLEncoded": false,
+ "isSerialized": true,
+ "isSigningField": true,
+ "type": "Amount"
+ }
+ ],
+ [
+ "LockedBalance",
+ {
+ "nth": 21,
+ "isVLEncoded": false,
+ "isSerialized": true,
+ "isSigningField": true,
+ "type": "Amount"
+ }
+ ],
+ [
+ "BaseFeeDrops",
+ {
+ "nth": 22,
+ "isVLEncoded": false,
+ "isSerialized": true,
+ "isSigningField": true,
+ "type": "Amount"
+ }
+ ],
+ [
+ "ReserveBaseDrops",
+ {
+ "nth": 23,
+ "isVLEncoded": false,
+ "isSerialized": true,
+ "isSigningField": true,
+ "type": "Amount"
+ }
+ ],
+ [
+ "ReserveIncrementDrops",
+ {
+ "nth": 24,
+ "isVLEncoded": false,
+ "isSerialized": true,
+ "isSigningField": true,
+ "type": "Amount"
+ }
+ ],
[
"PublicKey",
{
@@ -1661,6 +1786,16 @@
"type": "Blob"
}
],
+ [
+ "Blob",
+ {
+ "nth": 26,
+ "isVLEncoded": true,
+ "isSerialized": true,
+ "isSigningField": true,
+ "type": "Blob"
+ }
+ ],
[
"Account",
{
@@ -1801,6 +1936,16 @@
"type": "Vector256"
}
],
+ [
+ "HookNamespaces",
+ {
+ "nth": 5,
+ "isVLEncoded": true,
+ "isSerialized": true,
+ "isSigningField": true,
+ "type": "Vector256"
+ }
+ ],
[
"Paths",
{
@@ -2176,6 +2321,12 @@
"telCAN_NOT_QUEUE_BLOCKED": -389,
"telCAN_NOT_QUEUE_FEE": -388,
"telCAN_NOT_QUEUE_FULL": -387,
+ "telWRONG_NETWORK": -386,
+ "telREQUIRES_NETWORK_ID": -385,
+ "telNETWORK_ID_MAKES_TX_NON_CANONICAL": -384,
+ "telNON_LOCAL_EMITTED_TXN": -383,
+ "telIMPORT_VL_KEY_NOT_RECOGNISED": -382,
+ "telCAN_NOT_QUEUE_IMPORT": -381,
"temMALFORMED": -299,
"temBAD_AMOUNT": -298,
"temBAD_CURRENCY": -297,
@@ -2214,6 +2365,16 @@
"temUNKNOWN": -264,
"temSEQ_AND_TICKET": -263,
"temBAD_NFTOKEN_TRANSFER_FEE": -262,
+ "temAMM_BAD_TOKENS": -261,
+ "temXCHAIN_EQUAL_DOOR_ACCOUNTS": -260,
+ "temXCHAIN_BAD_PROOF": -259,
+ "temXCHAIN_BRIDGE_BAD_ISSUES": -258,
+ "temXCHAIN_BRIDGE_NONDOOR_OWNER": -257,
+ "temXCHAIN_BRIDGE_BAD_MIN_ACCOUNT_CREATE_AMOUNT": -256,
+ "temXCHAIN_BRIDGE_BAD_REWARD_AMOUNT": -255,
+ "temXCHAIN_TOO_MANY_ATTESTATIONS": -254,
+ "temHOOK_DATA_TOO_LARGE": -253,
+ "temHOOK_REJECTED": -252,
"tefFAILURE": -199,
"tefALREADY": -198,
"tefBAD_ADD_AUTH": -197,
@@ -2235,6 +2396,7 @@
"tefTOO_BIG": -181,
"tefNO_TICKET": -180,
"tefNFTOKEN_IS_NOT_TRANSFERABLE": -179,
+ "tefPAST_IMPORT_SEQ": -178,
"terRETRY": -99,
"terFUNDS_SPENT": -98,
"terINSUF_FEE_B": -97,
@@ -2247,6 +2409,8 @@
"terNO_RIPPLE": -90,
"terQUEUED": -89,
"terPRE_TICKET": -88,
+ "terNO_AMM": -87,
+ "terNO_HOOK": -86,
"tesSUCCESS": 0,
"tecCLAIM": 100,
"tecPATH_PARTIAL": 101,
@@ -2286,6 +2450,7 @@
"tecKILLED": 150,
"tecHAS_OBLIGATIONS": 151,
"tecTOO_SOON": 152,
+ "tecHOOK_REJECTED": 153,
"tecMAX_SEQUENCE_REACHED": 154,
"tecNO_SUITABLE_NFTOKEN_PAGE": 155,
"tecNFTOKEN_BUY_SELL_MISMATCH": 156,
@@ -2293,7 +2458,33 @@
"tecCANT_ACCEPT_OWN_NFTOKEN_OFFER": 158,
"tecINSUFFICIENT_FUNDS": 159,
"tecOBJECT_NOT_FOUND": 160,
- "tecINSUFFICIENT_PAYMENT": 161
+ "tecINSUFFICIENT_PAYMENT": 161,
+ "tecAMM_UNFUNDED": 162,
+ "tecAMM_BALANCE": 163,
+ "tecAMM_FAILED_DEPOSIT": 164,
+ "tecAMM_FAILED_WITHDRAW": 165,
+ "tecAMM_INVALID_TOKENS": 166,
+ "tecAMM_FAILED_BID": 167,
+ "tecAMM_FAILED_VOTE": 168,
+ "tecREQUIRES_FLAG": 169,
+ "tecPRECISION_LOSS": 170,
+ "tecBAD_XCHAIN_TRANSFER_ISSUE": 171,
+ "tecXCHAIN_NO_CLAIM_ID": 172,
+ "tecXCHAIN_BAD_CLAIM_ID": 173,
+ "tecXCHAIN_CLAIM_NO_QUORUM": 174,
+ "tecXCHAIN_PROOF_UNKNOWN_KEY": 175,
+ "tecXCHAIN_CREATE_ACCOUNT_NONXRP_ISSUE": 176,
+ "tecXCHAIN_WRONG_CHAIN": 177,
+ "tecXCHAIN_REWARD_MISMATCH": 178,
+ "tecXCHAIN_NO_SIGNERS_LIST": 179,
+ "tecXCHAIN_SENDING_ACCOUNT_MISMATCH": 180,
+ "tecXCHAIN_INSUFF_CREATE_AMOUNT": 181,
+ "tecXCHAIN_ACCOUNT_CREATE_PAST": 182,
+ "tecXCHAIN_ACCOUNT_CREATE_TOO_MANY": 183,
+ "tecXCHAIN_PAYMENT_FAILED": 184,
+ "tecXCHAIN_SELF_COMMIT": 185,
+ "tecXCHAIN_BAD_PUBLIC_KEY_ACCOUNT_PAIR": 186,
+ "tecLAST_POSSIBLE_ENTRY": 255
},
"TRANSACTION_TYPES": {
"Invalid": -1,
@@ -2325,8 +2516,16 @@
"NFTokenCreateOffer": 27,
"NFTokenCancelOffer": 28,
"NFTokenAcceptOffer": 29,
+ "URITokenMint": 45,
+ "URITokenBurn": 46,
+ "URITokenBuy": 47,
+ "URITokenCreateSellOffer": 48,
+ "URITokenCancelSellOffer": 49,
+ "Import": 97,
+ "Invoke": 99,
"EnableAmendment": 100,
"SetFee": 101,
- "UNLModify": 102
+ "UNLModify": 102,
+ "EmitFailure": 103
}
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 710 KiB

After

Width:  |  Height:  |  Size: 352 KiB

5
raw-loader.d.ts vendored
View File

@@ -2,3 +2,8 @@ declare module '*.md' {
const content: string
export default content
}
declare module '*.hook-bundle.js' {
const content: string
export default content
}

View File

@@ -1,5 +1,6 @@
import toast from 'react-hot-toast'
import state, { FaucetAccountRes } from '../index'
import fetchAccountInfo from '../../utils/accountInfo';
export const names = [
'Alice',
@@ -35,40 +36,37 @@ export const addFaucetAccount = async (name?: string, showToast: boolean = false
})
const json: FaucetAccountRes | { error: string } = await res.json()
if ('error' in json) {
if (showToast) {
return toast.error(json.error, { id: toastId })
} else {
return
}
} else {
if (showToast) {
toast.success('New account created', { id: toastId })
}
const currNames = state.accounts.map(acc => acc.name)
state.accounts.push({
name: name || names.filter(name => !currNames.includes(name))[0],
xrp: (json.xrp || 0 * 1000000).toString(),
address: json.address,
secret: json.secret,
sequence: 1,
hooks: [],
isLoading: false,
version: '2'
})
if (!showToast) return;
return toast.error(json.error, { id: toastId })
}
const currNames = state.accounts.map(acc => acc.name)
const info = await fetchAccountInfo(json.address, { silent: true })
state.accounts.push({
name: name || names.filter(name => !currNames.includes(name))[0],
xrp: (json.xrp || 0 * 1000000).toString(),
address: json.address,
secret: json.secret,
sequence: info?.Sequence || 1,
hooks: [],
isLoading: false,
version: '2'
})
if (showToast) {
toast.success('New account created', { id: toastId })
}
}
// fetch initial faucets
;(async function fetchFaucets() {
if (typeof window !== 'undefined') {
if (state.accounts.length === 0) {
await addFaucetAccount()
// setTimeout(() => {
// addFaucetAccount();
// }, 10000);
// fetch initial faucets
; (async function fetchFaucets() {
if (typeof window !== 'undefined') {
if (state.accounts.length === 0) {
await addFaucetAccount()
// setTimeout(() => {
// addFaucetAccount();
// }, 10000);
}
}
}
})()
})()
export const addFunds = async (address: string) => {
const toastId = toast.loading('Requesting funds')
@@ -79,7 +77,7 @@ export const addFunds = async (address: string) => {
if ('error' in json) {
return toast.error(json.error, { id: toastId })
} else {
toast.success(`Funds added (${json.xrp} XRP)`, { id: toastId })
toast.success(`Funds added (${json.xrp} XAH)`, { id: toastId })
const currAccount = state.accounts.find(acc => acc.address === address)
if (currAccount) {
currAccount.xrp = (Number(currAccount.xrp) + json.xrp * 1000000).toString()

View File

@@ -15,6 +15,11 @@ import { ref } from 'valtio'
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!')
}
@@ -26,7 +31,6 @@ export const compileCode = async (activeId: number) => {
// Set loading state to true
state.compiling = true
state.logs = []
const file = state.files[activeId]
try {
file.containsErrors = false
let res: Response
@@ -72,7 +76,7 @@ export const compileCode = async (activeId: number) => {
// Import wabt from and create human readable version of wasm file and
// put it into state
const ww = (await import('wabt')).default()
const ww = await (await import('wabt')).default()
const myModule = ww.readWasm(new Uint8Array(bufferData), {
readDebugNames: true
})
@@ -122,3 +126,46 @@ export const compileCode = async (activeId: number) => {
file.containsErrors = true
}
}
export const compileWat = async (activeId: number) => {
if (state.compiling) return;
const file = state.files[activeId]
state.compiling = true
state.logs = []
try {
const wabt = await (await import('wabt')).default()
const module = wabt.parseWat(file.name, file.content);
module.resolveNames();
module.validate();
const { buffer } = module.toBinary({
log: false,
write_debug_names: true,
});
file.compiledContent = ref(buffer)
file.lastCompiled = new Date()
file.compiledValueSnapshot = file.content
file.compiledWatContent = file.content
toast.success('Compiled successfully!', { position: 'bottom-center' })
state.logs.push({
type: 'success',
message: `File ${state.files?.[activeId]?.name} compiled successfully. Ready to deploy.`,
link: Router.asPath.replace('develop', 'deploy'),
linkText: 'Go to deploy'
})
} catch (err) {
console.log(err)
let message = "Error compiling WAT file!"
if (err instanceof Error) {
message = err.message
}
state.logs.push({
type: 'error',
message
})
toast.error(`Error occurred while compiling!`, { position: 'bottom-center' })
file.containsErrors = true
}
state.compiling = false
}

View File

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

View File

@@ -6,7 +6,9 @@ import calculateHookOn, { TTS } from '../../utils/hookOnCalculator'
import { Link } from '../../components'
import { ref } from 'valtio'
import estimateFee from '../../utils/estimateFee'
import { SetHookData } from '../../utils/setHook'
import { SetHookData, toHex } from '../../utils/setHook'
import ResultLink from '../../components/ResultLink'
import { xrplSend } from './xrpl-client'
export const sha256 = async (string: string) => {
const utf8 = new TextEncoder().encode(string)
@@ -16,13 +18,6 @@ export const sha256 = async (string: string) => {
return hashHex
}
function toHex(str: string) {
var result = ''
for (var i = 0; i < str.length; i++) {
result += str.charCodeAt(i).toString(16)
}
return result.toUpperCase()
}
function arrayBufferToHex(arrayBuffer?: ArrayBuffer | null) {
if (!arrayBuffer) {
@@ -63,9 +58,6 @@ export const prepareDeployHookTx = async (
if (!activeFile?.compiledContent) {
return
}
if (!state.client) {
return
}
const HookNamespace = (await sha256(data.HookNamespace)).toUpperCase()
const hookOnValues: (keyof TTS)[] = data.Invoke.map(tt => tt.value)
const { HookParameters } = data
@@ -86,187 +78,184 @@ export const prepareDeployHookTx = async (
// }
// }
// });
if (typeof window !== 'undefined') {
const tx = {
Account: account.address,
TransactionType: 'SetHook',
Sequence: account.sequence,
Fee: data.Fee,
Hooks: [
{
Hook: {
CreateCode: arrayBufferToHex(activeFile?.compiledContent).toUpperCase(),
HookOn: calculateHookOn(hookOnValues),
HookNamespace,
HookApiVersion: 0,
Flags: 1,
// ...(filteredHookGrants.length > 0 && { HookGrants: filteredHookGrants }),
...(filteredHookParameters.length > 0 && {
HookParameters: filteredHookParameters
})
}
if (typeof window === 'undefined') return
const tx = {
Account: account.address,
TransactionType: 'SetHook',
Sequence: account.sequence,
Fee: data.Fee,
NetworkID: process.env.NEXT_PUBLIC_NETWORK_ID,
Hooks: [
{
Hook: {
CreateCode: arrayBufferToHex(activeFile?.compiledContent).toUpperCase(),
HookOn: calculateHookOn(hookOnValues),
HookNamespace,
HookApiVersion: 0,
Flags: 1,
// ...(filteredHookGrants.length > 0 && { HookGrants: filteredHookGrants }),
...(filteredHookParameters.length > 0 && {
HookParameters: filteredHookParameters
})
}
]
}
return tx
}
]
}
return tx
}
/* deployHook function turns the wasm binary into
* hex string, signs the transaction and deploys it to
* Hooks testnet.
/*
* Turns the wasm binary into hex string, signs the transaction and deploys it to Hooks testnet.
*/
export const deployHook = async (account: IAccount & { name?: string }, data: SetHookData) => {
if (typeof window !== 'undefined') {
const activeFile = state.files[state.active]?.compiledContent
? state.files[state.active]
: state.files.filter(file => file.compiledContent)[0]
state.deployValues[activeFile.name] = data
const tx = await prepareDeployHookTx(account, data)
if (!tx) {
return
}
if (!state.client) {
return
}
const keypair = derive.familySeed(account.secret)
const activeFile = state.files[state.active]?.compiledContent
? state.files[state.active]
: state.files.filter(file => file.compiledContent)[0]
state.deployValues[activeFile.name] = data
const { signedTransaction } = sign(tx, keypair)
const currentAccount = state.accounts.find(acc => acc.address === account.address)
if (currentAccount) {
currentAccount.isLoading = true
}
let submitRes
const tx = await prepareDeployHookTx(account, data)
if (!tx) {
return
}
const keypair = derive.familySeed(account.secret)
const { signedTransaction } = sign(tx, keypair)
try {
submitRes = await state.client?.send({
command: 'submit',
tx_blob: signedTransaction
const currentAccount = state.accounts.find(acc => acc.address === account.address)
if (currentAccount) {
currentAccount.isLoading = true
}
let submitRes
try {
submitRes = await xrplSend({
command: 'submit',
tx_blob: signedTransaction
})
const txHash = submitRes.tx_json?.hash
const resultMsg = ref(
<>
[<ResultLink result={submitRes.engine_result} />] {submitRes.engine_result_message}{' '}
{txHash && (
<>
Transaction hash:{' '}
<Link
as="a"
href={`https://${process.env.NEXT_PUBLIC_EXPLORER_URL}/${txHash}`}
target="_blank"
rel="noopener noreferrer"
>
{txHash}
</Link>
</>
)}
</>
)
if (submitRes.engine_result === 'tesSUCCESS') {
state.deployLogs.push({
type: 'success',
message: 'Hook deployed successfully ✅'
})
if (submitRes.engine_result === 'tesSUCCESS') {
state.deployLogs.push({
type: 'success',
message: 'Hook deployed successfully ✅'
})
state.deployLogs.push({
type: 'success',
message: ref(
<>
[{submitRes.engine_result}] {submitRes.engine_result_message} Transaction hash:{' '}
<Link
as="a"
href={`https://${process.env.NEXT_PUBLIC_EXPLORER_URL}/${submitRes.tx_json?.hash}`}
target="_blank"
rel="noopener noreferrer"
>
{submitRes.tx_json?.hash}
</Link>
</>
)
// message: `[${submitRes.engine_result}] ${submitRes.engine_result_message} Validated ledger index: ${submitRes.validated_ledger_index}`,
})
} else {
state.deployLogs.push({
type: 'error',
message: `[${submitRes.engine_result || submitRes.error}] ${
submitRes.engine_result_message || submitRes.error_exception
}`
})
}
} catch (err) {
console.log(err)
state.deployLogs.push({
type: 'success',
message: resultMsg
})
} else if (submitRes.engine_result) {
state.deployLogs.push({
type: 'error',
message: 'Error occurred while deploying'
message: resultMsg
})
} else {
state.deployLogs.push({
type: 'error',
message: `[${submitRes.error}] ${submitRes.error_exception}`
})
}
if (currentAccount) {
currentAccount.isLoading = false
}
return submitRes
} catch (err) {
console.error(err)
state.deployLogs.push({
type: 'error',
message: 'Error occurred while deploying'
})
}
if (currentAccount) {
currentAccount.isLoading = false
}
return submitRes
}
export const deleteHook = async (account: IAccount & { name?: string }) => {
if (!state.client) {
return
}
const currentAccount = state.accounts.find(acc => acc.address === account.address)
if (currentAccount?.isLoading || !currentAccount?.hooks.length) {
return
}
if (typeof window !== 'undefined') {
const tx = {
Account: account.address,
TransactionType: 'SetHook',
Sequence: account.sequence,
Fee: '100000',
Hooks: [
{
Hook: {
CreateCode: '',
Flags: 1
}
const tx = {
Account: account.address,
TransactionType: 'SetHook',
Sequence: account.sequence,
Fee: '100000',
NetworkID: process.env.NEXT_PUBLIC_NETWORK_ID,
Hooks: [
{
Hook: {
CreateCode: '',
Flags: 1
}
]
}
const keypair = derive.familySeed(account.secret)
try {
// Update tx Fee value with network estimation
const res = await estimateFee(tx, account)
tx['Fee'] = res?.base_fee ? res?.base_fee : '1000'
} catch (err) {
// use default value what you defined earlier
console.log(err)
}
const { signedTransaction } = sign(tx, keypair)
if (currentAccount) {
currentAccount.isLoading = true
}
let submitRes
const toastId = toast.loading('Deleting hook...')
try {
submitRes = await state.client.send({
command: 'submit',
tx_blob: signedTransaction
})
if (submitRes.engine_result === 'tesSUCCESS') {
toast.success('Hook deleted successfully ✅', { id: toastId })
state.deployLogs.push({
type: 'success',
message: 'Hook deleted successfully ✅'
})
state.deployLogs.push({
type: 'success',
message: `[${submitRes.engine_result}] ${submitRes.engine_result_message} Validated ledger index: ${submitRes.validated_ledger_index}`
})
currentAccount.hooks = []
} else {
toast.error(`${submitRes.engine_result_message || submitRes.error_exception}`, {
id: toastId
})
state.deployLogs.push({
type: 'error',
message: `[${submitRes.engine_result || submitRes.error}] ${
submitRes.engine_result_message || submitRes.error_exception
}`
})
}
} catch (err) {
console.log(err)
toast.error('Error occurred while deleting hook', { id: toastId })
]
}
const keypair = derive.familySeed(account.secret)
try {
// Update tx Fee value with network estimation
const res = await estimateFee(tx, account)
tx['Fee'] = res?.base_fee || '1000'
} catch (err) {
console.error(err)
}
const { signedTransaction } = sign(tx, keypair)
if (currentAccount) {
currentAccount.isLoading = true
}
let submitRes
const toastId = toast.loading('Deleting hook...')
try {
submitRes = await xrplSend({
command: 'submit',
tx_blob: signedTransaction
})
if (submitRes.engine_result === 'tesSUCCESS') {
toast.success('Hook deleted successfully ✅', { id: toastId })
state.deployLogs.push({
type: 'success',
message: 'Hook deleted successfully ✅'
})
state.deployLogs.push({
type: 'success',
message: `[${submitRes.engine_result}] ${submitRes.engine_result_message} Validated ledger index: ${submitRes.validated_ledger_index}`
})
currentAccount.hooks = []
} else {
toast.error(`${submitRes.engine_result_message || submitRes.error_exception}`, {
id: toastId
})
state.deployLogs.push({
type: 'error',
message: 'Error occurred while deleting hook'
message: `[${submitRes.engine_result || submitRes.error}] ${
submitRes.engine_result_message || submitRes.error_exception
}`
})
}
if (currentAccount) {
currentAccount.isLoading = false
}
return submitRes
} catch (err) {
console.log(err)
toast.error('Error occurred while deleting hook', { id: toastId })
state.deployLogs.push({
type: 'error',
message: 'Error occurred while deleting hook'
})
}
if (currentAccount) {
currentAccount.isLoading = false
}
return submitRes
}

View File

@@ -24,19 +24,28 @@ export const fetchFiles = async (gistId: string) => {
.includes(id)
if (isTemplate(gistId)) {
// fetch headers
const headerRes = await fetch(
`${process.env.NEXT_PUBLIC_COMPILE_API_BASE_URL}/api/header-files`
)
if (!headerRes.ok) throw Error('Failed to fetch headers')
const headerJson = await headerRes.json()
const headerFiles: Record<string, { filename: string; content: string; language: string }> =
const template = Object.values(templateFileIds).find(tmp => tmp.id === gistId)
let headerFiles: Record<string, { filename: string; content: string; language: string }> =
{}
Object.entries(headerJson).forEach(([key, value]) => {
const fname = `${key}.h`
headerFiles[fname] = { filename: fname, content: value as string, language: 'C' }
})
if (template?.headerId) {
const resHeader = await octokit.request('GET /gists/{gist_id}', { gist_id: template.headerId })
if (!resHeader.data.files) throw new Error('No header files could be fetched from given gist id!')
headerFiles = resHeader.data.files as any
} else {
// fetch headers
const headerRes = await fetch(
`${process.env.NEXT_PUBLIC_COMPILE_API_BASE_URL}/api/header-files`
)
if (!headerRes.ok) throw Error('Failed to fetch headers')
const headerJson = await headerRes.json()
const headerFiles: Record<string, { filename: string; content: string; language: string }> =
{}
Object.entries(headerJson).forEach(([key, value]) => {
const fname = `${key}.h`
headerFiles[fname] = { filename: fname, content: value as string, language: 'C' }
})
}
const files = {
...res.data.files,
...headerFiles
@@ -61,6 +70,7 @@ export const fetchFiles = async (gistId: string) => {
// default priority is undefined == 0
const extPriority: Record<string, number> = {
c: 3,
wat: 3,
md: 2,
h: -1
}

View File

@@ -2,12 +2,14 @@ import { derive, sign } from 'xrpl-accountlib'
import state from '..'
import type { IAccount } from '..'
import ResultLink from '../../components/ResultLink'
import { ref } from 'valtio'
import { xrplSend } from './xrpl-client'
interface TransactionOptions {
TransactionType: string
Account?: string
Fee?: string
Destination?: string
[index: string]: any
}
interface OtherOptions {
@@ -19,34 +21,46 @@ export const sendTransaction = async (
txOptions: TransactionOptions,
options?: OtherOptions
) => {
if (!state.client) throw Error('XRPL client not initalized')
const { Fee = '1000', ...opts } = txOptions
const tx: TransactionOptions = {
Account: account.address,
Sequence: account.sequence,
Fee, // TODO auto-fillable default
Fee,
NetworkID: process.env.NEXT_PUBLIC_NETWORK_ID,
...opts
}
const { logPrefix = '' } = options || {}
state.transactionLogs.push({
type: 'log',
message: `${logPrefix}${JSON.stringify(tx, null, 2)}`
})
try {
const signedAccount = derive.familySeed(account.secret)
const { signedTransaction } = sign(tx, signedAccount)
const response = await state.client.send({
const response = await xrplSend({
command: 'submit',
tx_blob: signedTransaction
})
const resultMsg = ref(
<>
{logPrefix}[<ResultLink result={response.engine_result} />] {response.engine_result_message}
</>
)
if (response.engine_result === 'tesSUCCESS') {
state.transactionLogs.push({
type: 'success',
message: `${logPrefix}[${response.engine_result}] ${response.engine_result_message}`
message: resultMsg
})
} else if (response.engine_result) {
state.transactionLogs.push({
type: 'error',
message: resultMsg
})
} else {
state.transactionLogs.push({
type: 'error',
message: `${logPrefix}[${response.error || response.engine_result}] ${
response.error_exception || response.engine_result_message
}`
message: `${logPrefix}[${response.error}] ${response.error_exception}`
})
}
const currAcc = state.accounts.find(acc => acc.address === account.address)

View File

@@ -0,0 +1,7 @@
import { XrplClient } from 'xrpl-client';
import state from '..';
export const xrplSend = async(...params: Parameters<XrplClient['send']>) => {
const client = await state.client.ready()
return client.send(...params);
}

82
state/constants/flags.ts Normal file
View File

@@ -0,0 +1,82 @@
import { SelectOption } from '../transactions';
interface Flags {
[key: string]: string;
}
export const transactionFlags: { [key: /* TransactionType */ string]: Flags } = {
"*": {
tfFullyCanonicalSig: '0x80000000'
},
Payment: {
tfNoDirectRipple: '0x00010000',
tfPartialPayment: '0x00020000',
tfLimitQuality: '0x00040000',
},
AccountSet: {
tfRequireDestTag: '0x00010000',
tfOptionalDestTag: '0x00020000',
tfRequireAuth: '0x00040000',
tfOptionalAuth: '0x00080000',
tfDisallowXRP: '0x00100000',
tfAllowXRP: '0x00200000',
},
NFTokenCreateOffer: {
tfSellNFToken: '0x00000001',
},
NFTokenMint: {
tfBurnable: '0x00000001',
tfOnlyXRP: '0x00000002',
tfTrustLine: '0x00000004',
tfTransferable: '0x00000008',
},
OfferCreate: {
tfPassive: '0x00010000',
tfImmediateOrCancel: '0x00020000',
tfFillOrKill: '0x00040000',
tfSell: '0x00080000',
},
PaymentChannelClaim: {
tfRenew: '0x00010000',
tfClose: '0x00020000',
},
TrustSet: {
tfSetfAuth: '0x00010000',
tfSetNoRipple: '0x00020000',
tfClearNoRipple: '0x00040000',
tfSetFreeze: '0x00100000',
tfClearFreeze: '0x00200000',
},
URITokenMint: {
tfBurnable: '0x00000001',
},
}
export const getFlags = (tt?: string) => {
if (!tt) return
const flags = {
...transactionFlags['*'],
...transactionFlags[tt]
}
return flags
}
export function combineFlags(flags?: string[]): string | undefined {
if (!flags) return
const num = flags.reduce((cumm, curr) => cumm | BigInt(curr), BigInt(0))
return num.toString()
}
export function extractFlags(transactionType: string, flags?: string | number,): SelectOption[] {
const flagsObj = getFlags(transactionType)
if (!flags || !flagsObj) return []
const extracted = Object.entries(flagsObj).reduce((cumm, [label, value]) => {
return (BigInt(flags) & BigInt(value)) ? cumm.concat({ label, value }) : cumm
}, [] as SelectOption[])
return extracted
}

View File

@@ -4,36 +4,49 @@ import Notary from '../../components/icons/Notary'
import Peggy from '../../components/icons/Peggy'
import Starter from '../../components/icons/Starter'
export const templateFileIds = {
type Template = {
id: string
name: string
description: string
headerId?: string
icon: () => JSX.Element
}
export const templateFileIds: Record<string, Template> = {
starter: {
id: '1f8109c80f504e6326db2735df2f0ad6', // Forked
name: 'Starter',
description:
'Just a basic starter with essential imports, just accepts any transaction coming through',
headerId: '028e8ce6d6d674776970caf8acc77ecc',
icon: Starter
},
firewall: {
id: '1cc30f39c8a0b9c55b88c312669ca45e', // Forked
name: 'Firewall',
description: 'This Hook essentially checks a blacklist of accounts',
headerId: '028e8ce6d6d674776970caf8acc77ecc',
icon: Firewall
},
notary: {
id: '87b6f5a8c2f5038fb0f20b8b510efa10', // Forked
name: 'Notary',
description: 'Collecting signatures for multi-sign transactions',
headerId: '028e8ce6d6d674776970caf8acc77ecc',
icon: Notary
},
carbon: {
id: '953662b22d065449f8ab6f69bc2afe41', // Forked
name: 'Carbon',
description: 'Send a percentage of sum to an address',
headerId: '028e8ce6d6d674776970caf8acc77ecc',
icon: Carbon
},
peggy: {
id: '049784a83fa068faf7912f663f7b6471', // Forked
name: 'Peggy',
description: 'An oracle based stable coin hook',
headerId: '028e8ce6d6d674776970caf8acc77ecc',
icon: Peggy
}
}

View File

@@ -78,7 +78,7 @@ export interface IState {
splits: {
[id: string]: SplitSize
}
client: XrplClient | null
client: XrplClient
clientStatus: 'offline' | 'online'
mainModalOpen: boolean
mainModalShowed: boolean
@@ -113,7 +113,7 @@ let initialState: IState = {
tabSize: 2
},
splits: {},
client: null,
client: undefined!, // set below only.
clientStatus: 'offline' as 'offline',
mainModalOpen: false,
mainModalShowed: false,
@@ -153,9 +153,9 @@ const state = proxy<IState>({
})
// Initialize socket connection
const client = new XrplClient(`wss://${process.env.NEXT_PUBLIC_TESTNET_URL}`)
state.client = ref(client);
client.on('online', () => {
state.client = ref(client)
state.clientStatus = 'online'
})

View File

@@ -4,33 +4,56 @@ import transactionsData from '../content/transactions.json'
import state from '.'
import { showAlert } from '../state/actions/showAlert'
import { parseJSON } from '../utils/json'
import { extractFlags, getFlags } from './constants/flags'
import { fromHex } from '../utils/setHook'
import { typeIs } from '../utils/helpers'
export type SelectOption = {
value: string
label: string
}
export type HookParameters = {
[key: string]: SelectOption
}
export type Memos = {
[key: string]: {
type: string
format: string
data: string
}
}
export interface TransactionState {
selectedTransaction: SelectOption | null
selectedAccount: SelectOption | null
selectedDestAccount: SelectOption | null
selectedFlags: SelectOption[] | null
hookParameters: HookParameters
memos: Memos
txIsLoading: boolean
txIsDisabled: boolean
txFields: TxFields
viewType: 'json' | 'ui'
editorValue?: string
editorIsSaved: boolean
estimatedFee?: string
}
const commonFields = ['TransactionType', 'Account', 'Sequence', "HookParameters"] as const;
export type TxFields = Omit<
Partial<typeof transactionsData[0]>,
'Account' | 'Sequence' | 'TransactionType'
typeof commonFields[number]
>
export const defaultTransaction: TransactionState = {
selectedTransaction: null,
selectedAccount: null,
selectedDestAccount: null,
selectedFlags: null,
hookParameters: {},
memos: {},
editorIsSaved: true,
txIsLoading: false,
txIsDisabled: false,
txFields: {},
@@ -106,47 +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) {
@@ -156,7 +183,7 @@ export const prepareState = (value: string, transactionType?: string) => {
return
}
const { Account, TransactionType, Destination, ...rest } = options
const { Account, TransactionType, HookParameters, Memos, ...rest } = options
let tx: Partial<TransactionState> = {}
const schema = getTxFields(transactionType)
@@ -186,39 +213,58 @@ export const prepareState = (value: string, transactionType?: string) => {
tx.selectedTransaction = null
}
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 (HookParameters && HookParameters instanceof Array) {
tx.hookParameters = HookParameters.reduce<TransactionState["hookParameters"]>((acc, cur, idx) => {
const param = { label: fromHex(cur.HookParameter?.HookParameterName || ""), value: cur.HookParameter?.HookParameterValue || "" }
acc[idx] = param;
return acc;
}, {})
}
if (Memos && Memos instanceof Array) {
tx.memos = Memos.reduce<TransactionState["memos"]>((acc, cur, idx) => {
const memo = { data: cur.Memo?.MemoData || "", type: fromHex(cur.Memo?.MemoType || ""), format: fromHex(cur.Memo?.MemoFormat || "") }
acc[idx] = memo;
return acc;
}, {})
}
if (getFlags(TransactionType) && rest.Flags) {
const flags = extractFlags(TransactionType, rest.Flags)
rest.Flags = undefined
tx.selectedFlags = flags
}
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
@@ -227,6 +273,7 @@ export const prepareState = (value: string, transactionType?: string) => {
})
tx.txFields = rest
tx.editorIsSaved = true;
return tx
}
@@ -237,12 +284,12 @@ export const getTxFields = (tt?: string) => {
if (!txFields) return {}
let _txFields = Object.keys(txFields)
.filter(key => !['TransactionType', 'Account', 'Sequence'].includes(key))
.filter(key => !commonFields.includes(key as any))
.reduce<TxFields>((tf, key) => ((tf[key as keyof TxFields] = (txFields as any)[key]), tf), {})
return _txFields
}
export { transactionsData }
export { transactionsData, commonFields }
export const transactionsOptions = transactionsData.map(tx => ({
value: tx.TransactionType,

31
utils/accountInfo.ts Normal file
View File

@@ -0,0 +1,31 @@
import toast from 'react-hot-toast'
import { xrplSend } from '../state/actions/xrpl-client'
interface AccountInfo {
Account: string,
Sequence: number,
Flags: number,
Balance?: string,
}
const fetchAccountInfo = async (
address: string,
opts: { silent?: boolean } = {}
): Promise<AccountInfo | undefined> => {
try {
const res = await xrplSend({
id: `hooks-builder-req-info-${address}`,
command: 'account_info',
account: address
})
return res.account_data;
} catch (err) {
if (!opts.silent) {
console.error(err)
toast.error('Could not fetch account info!')
}
}
}
export default fetchAccountInfo

View File

@@ -1,6 +1,7 @@
import toast from 'react-hot-toast'
import { derive, sign } from 'xrpl-accountlib'
import state, { IAccount } from '../state'
import { IAccount } from '../state'
import { xrplSend } from '../state/actions/xrpl-client'
const estimateFee = async (
tx: Record<string, unknown>,
@@ -22,7 +23,10 @@ const estimateFee = async (
const keypair = derive.familySeed(account.secret)
const { signedTransaction } = sign(copyTx, keypair)
const res = await state.client?.send({ command: 'fee', tx_blob: signedTransaction })
const res = await xrplSend({ command: 'fee', tx_blob: signedTransaction })
if (res.error) {
throw new Error(`[${res.error}] ${res.error_exception}.`);
}
if (res && res.drops) {
return res.drops
}
@@ -30,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
}

View File

@@ -19,3 +19,17 @@ export const getFileExtention = (filename?: string): string | undefined => {
const ext = (filename.includes('.') && filename.split('.').pop()) || undefined
return ext
}
type Type = "array" | "undefined" | "object" | "string" | "number" | "bigint" | "boolean" | "symbol" | "function"
type obj = Record<string | number | symbol, unknown>
type arr = unknown[]
export const typeIs = <T extends Type>(arg: any, t: T | T[]): arg is unknown & (T extends "array" ? arr : T extends "undefined" ? undefined | null : T extends "object" ? obj : T extends "string" ? string : T extends "number" ? number : T extends "bigint" ? bigint : T extends "boolean" ? boolean : T extends "symbol" ? symbol : T extends "function" ? Function : never) => {
const types = Array.isArray(t) ? t : [t]
return types.includes(typeOf(arg) as T);
}
export const typeOf = (arg: any): Type => {
const type = arg instanceof Array ? 'array' : arg === null ? 'undefined' : typeof arg
return type;
}

View File

@@ -8,6 +8,7 @@ export const tts = {
ttOFFER_CREATE: 7,
ttOFFER_CANCEL: 8,
ttTICKET_CREATE: 10,
ttTICKET_CANCEL: 11,
ttSIGNER_LIST_SET: 12,
ttPAYCHAN_CREATE: 13,
ttPAYCHAN_FUND: 14,
@@ -18,28 +19,28 @@ export const tts = {
ttDEPOSIT_PREAUTH: 19,
ttTRUST_SET: 20,
ttACCOUNT_DELETE: 21,
ttHOOK_SET: 22,
ttNFTOKEN_MINT: 25,
ttNFTOKEN_BURN: 26,
ttNFTOKEN_CREATE_OFFER: 27,
ttNFTOKEN_CANCEL_OFFER: 28,
ttNFTOKEN_ACCEPT_OFFER: 29
ttSET_HOOK: 22,
ttURI_TOKEN_MINT: 45,
ttURI_TOKEN_BURN: 46,
ttURI_TOKEN_BUY: 47,
ttURI_TOKEN_CREATE_SELL_OFFER: 48,
ttURI_TOKEN_CANCEL_SELL_OFFER: 49,
ttIMPORT: 97,
ttINVOKE: 99
}
export type TTS = typeof tts
const calculateHookOn = (arr: (keyof TTS)[]) => {
let start = '0x000000003e3ff5bf'
let s = '0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffbfffff'
arr.forEach(n => {
let v = BigInt(start)
v ^= BigInt(1) << BigInt(tts[n as keyof TTS])
let s = v.toString(16)
let l = s.length
if (l < 16) s = '0'.repeat(16 - l) + s
s = '0x' + s
start = s
let v = BigInt(s)
v ^= BigInt(1) << BigInt(tts[n])
s = "0x" + v.toString(16)
})
return start.substring(2)
s = s.replace('0x', '')
s = s.padStart(64, '0')
return s
}
export default calculateHookOn

File diff suppressed because one or more lines are too long

View File

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

View File

@@ -20,6 +20,13 @@ export type SetHookData = {
}
$metaData?: any
}[]
Memos?: {
Memo: {
MemoType: string,
MemoData: string
MemoFormat: string
}
}[]
// HookGrants: {
// HookGrant: {
// Authorize: string;
@@ -65,12 +72,22 @@ export const getInvokeOptions = (content?: string) => {
label: opt,
value: opt
}))
// default
if (!invokeOptions.length) {
const payment = transactionOptions.find(tx => tx.value === 'ttPAYMENT')
if (payment) return [payment]
}
return invokeOptions
}
export function toHex(str: string) {
var result = ''
for (var i = 0; i < str.length; i++) {
const hex = str.charCodeAt(i).toString(16)
result += hex.padStart(2, '0')
}
return result.toUpperCase()
}
export function fromHex(hex: string) {
var str = ''
for (var i = 0; i < hex.length; i += 2) {
str += String.fromCharCode(parseInt(hex.substring(i, i + 2), 16))
}
return str
}

View File

@@ -22,6 +22,7 @@ import hooksFloatManipPure from './md/hooks-float-manip-pure.md'
import hooksFloatOnePure from './md/hooks-float-one-pure.md'
import hooksFloatPure from './md/hooks-float-pure.md'
import hooksGuardCalled from './md/hooks-guard-called.md'
import hooksGuardCallNonConst from './md/hooks-guard-call-non-const.md'
import hooksGuardInFor from './md/hooks-guard-in-for.md'
import hooksGuardInWhile from './md/hooks-guard-in-while.md'
import hooksHashBufLen from './md/hooks-hash-buf-len.md'
@@ -70,6 +71,7 @@ const docs: { [key: string]: string } = {
'hooks-float-one-pure': hooksFloatOnePure,
'hooks-float-pure': hooksFloatPure,
'hooks-guard-called': hooksGuardCalled,
'hooks-guard-call-non-const': hooksGuardCallNonConst,
'hooks-guard-in-for': hooksGuardInFor,
'hooks-guard-in-while': hooksGuardInWhile,
'hooks-hash-buf-len': hooksHashBufLen,

View File

@@ -1,5 +1,5 @@
# hooks-account-buf-len
Function [hook_account](https://xrpl-hooks.readme.io/v2.0/reference/hook_account) has fixed-size account ID output.
Function [hook_account](https://xrpl-hooks.readme.io/reference/hook_account) has fixed-size account ID output.
This check warns about too-small size of its output buffer (if it's specified by a constant - variable parameter is ignored).

View File

@@ -1,5 +1,5 @@
# hooks-account-conv-buf-len
Function [util_raddr](https://xrpl-hooks.readme.io/v2.0/reference/util_raddr) has fixed-size account ID input.
Function [util_raddr](https://xrpl-hooks.readme.io/reference/util_raddr) has fixed-size account ID input.
This check warns unless the correct size is passed in the input size parameter (if it's specified by a constant - variable parameter is ignored).

View File

@@ -1,5 +1,5 @@
# hooks-account-conv-pure
Hooks identify accounts by the 20 byte account ID, which can be converted to an raddr using the [util_raddr](https://xrpl-hooks.readme.io/v2.0/reference/util_raddr) function. If the account ID never changes, a more efficient way to do this is precompute the raddr from the account ID.
Hooks identify accounts by the 20 byte account ID, which can be converted to an raddr using the [util_raddr](https://xrpl-hooks.readme.io/reference/util_raddr) function. If the account ID never changes, a more efficient way to do this is precompute the raddr from the account ID.
This check warns about calls of `util_raddr` with constant input and proposes to add a tracing statement showing the computed value (so that the user can use it to replace the call).

View File

@@ -1,7 +1,7 @@
# hooks-array-buf-len
Hook API [sto_subarray](https://xrpl-hooks.readme.io/v2.0/reference/sto_subarray) requires non-empty input buffer and takes a parameter specifying the array index, whose value is limited - the sought object cannot be found if the limit is exceeded.
Hook API [sto_subarray](https://xrpl-hooks.readme.io/reference/sto_subarray) requires non-empty input buffer and takes a parameter specifying the array index, whose value is limited - the sought object cannot be found if the limit is exceeded.
This check warns about empty input as well as too-large values of the index specified in calls to `sto_subarray` (if they're specified by constants - variable parameters are ignored).
[Read more](https://xrpl-hooks.readme.io/v2.0/docs/serialized-objects)
[Read more](https://xrpl-hooks.readme.io/docs/serialized-objects)

View File

@@ -1,3 +1,3 @@
# hooks-burden-prereq
Hook API [etxn_burden](https://xrpl-hooks.readme.io/v2.0/reference/etxn_burden) computes transaction burden, based on (i.a.) the number of reserved transactions, so a call to it must be preceded by a call to [etxn_reserve](https://xrpl-hooks.readme.io/v2.0/reference/etxn_reserve).
Hook API [etxn_burden](https://xrpl-hooks.readme.io/reference/etxn_burden) computes transaction burden, based on (i.a.) the number of reserved transactions, so a call to it must be preceded by a call to [etxn_reserve](https://xrpl-hooks.readme.io/reference/etxn_reserve).

View File

@@ -1,5 +1,5 @@
# hooks-control-string-arg
Functions [accept](https://xrpl-hooks.readme.io/v2.0/reference/accept) and [rollback](https://xrpl-hooks.readme.io/v2.0/reference/rollback) take an optional string buffer stored outside the hook as its result message. This is useful for debugging but takes up space.
Functions [accept](https://xrpl-hooks.readme.io/reference/accept) and [rollback](https://xrpl-hooks.readme.io/reference/rollback) take an optional string buffer stored outside the hook as its result message. This is useful for debugging but takes up space.
For a release version, this check warns about constant strings passed to `accept` and `rollback`.

View File

@@ -1,7 +1,7 @@
# hooks-detail-buf-len
Function [etxn_details](https://xrpl-hooks.readme.io/v2.0/reference/etxn_details) has fixed-size sfEmitDetails output.
Function [etxn_details](https://xrpl-hooks.readme.io/reference/etxn_details) has fixed-size sfEmitDetails output.
This check warns about too-small size of its output buffer (if it's specified by a constant - variable parameter is ignored).
[Read more](https://xrpl-hooks.readme.io/v2.0/docs/emitted-transactions)
[Read more](https://xrpl-hooks.readme.io/docs/emitted-transactions)

View File

@@ -1,5 +1,5 @@
# hooks-detail-prereq
Hook API [etxn_details](https://xrpl-hooks.readme.io/v2.0/reference/etxn_details) serializes emit details, based on (i.a.) the number of reserved transactions, so a call to it must be preceded by a call to [etxn_reserve](https://xrpl-hooks.readme.io/v2.0/reference/etxn_reserve).
Hook API [etxn_details](https://xrpl-hooks.readme.io/reference/etxn_details) serializes emit details, based on (i.a.) the number of reserved transactions, so a call to it must be preceded by a call to [etxn_reserve](https://xrpl-hooks.readme.io/reference/etxn_reserve).
[Read more](https://xrpl-hooks.readme.io/v2.0/docs/emitted-transactions)
[Read more](https://xrpl-hooks.readme.io/docs/emitted-transactions)

View File

@@ -1,7 +1,7 @@
# hooks-emit-buf-len
Function [emit](https://xrpl-hooks.readme.io/v2.0/reference/emit) has fixed-size transaction hash output.
Function [emit](https://xrpl-hooks.readme.io/reference/emit) has fixed-size transaction hash output.
This check warns about too-small size of its output buffer (if it's specified by a constant - variable parameter is ignored).
[Read more](https://xrpl-hooks.readme.io/v2.0/docs/emitted-transactions)
[Read more](https://xrpl-hooks.readme.io/docs/emitted-transactions)

View File

@@ -1,5 +1,5 @@
# hooks-emit-prereq
Before emitting a transaction using [emit](https://xrpl-hooks.readme.io/v2.0/reference/emit) Hook API, a hook must set a maximal count of transactions it plans to emit, by calling [etxn_reserve](https://xrpl-hooks.readme.io/v2.0/reference/etxn_reserve).
Before emitting a transaction using [emit](https://xrpl-hooks.readme.io/reference/emit) Hook API, a hook must set a maximal count of transactions it plans to emit, by calling [etxn_reserve](https://xrpl-hooks.readme.io/reference/etxn_reserve).
[Read more](https://xrpl-hooks.readme.io/v2.0/docs/emitted-transactions)
[Read more](https://xrpl-hooks.readme.io/docs/emitted-transactions)

View File

@@ -2,4 +2,4 @@
Recursive calls are disallowed in the implementation of hook entry points.
[Read more](https://xrpl-hooks.readme.io/v2.0/docs/loops-and-guarding#no-recursion)
[Read more](https://xrpl-hooks.readme.io/docs/loops-and-guarding#no-recursion)

View File

@@ -2,4 +2,4 @@
Shows error on function definitions with unexpected (that is, neither `hook` nor `cbak`) names.
[Read more](https://xrpl-hooks.readme.io/v2.0/docs/compiling-hooks#constraints)
[Read more](https://xrpl-hooks.readme.io/docs/compiling-hooks#constraints)

View File

@@ -1,7 +1,7 @@
# hooks-entry-points
A Hook always implements and exports a [hook](https://xrpl-hooks.readme.io/v2.0/reference/hook) function.
A Hook always implements and exports a [hook](https://xrpl-hooks.readme.io/reference/hook) function.
This check shows error on translation units that do not have it.
[Read more](https://xrpl-hooks.readme.io/v2.0/docs/compiling-hooks)
[Read more](https://xrpl-hooks.readme.io/docs/compiling-hooks)

View File

@@ -1,5 +1,5 @@
# hooks-fee-prereq
Hook API [etxn_fee_base](https://xrpl-hooks.readme.io/v2.0/reference/etxn_fee_base) estimates a transaction fee, based on (i.a.) the number of reserved transactions, so a call to it must be preceded by a call to [etxn_reserve](https://xrpl-hooks.readme.io/v2.0/reference/etxn_reserve).
Hook API [etxn_fee_base](https://xrpl-hooks.readme.io/reference/etxn_fee_base) estimates a transaction fee, based on (i.a.) the number of reserved transactions, so a call to it must be preceded by a call to [etxn_reserve](https://xrpl-hooks.readme.io/reference/etxn_reserve).
[Read more](https://xrpl-hooks.readme.io/v2.0/docs/hook-fees)
[Read more](https://xrpl-hooks.readme.io/docs/hook-fees)

View File

@@ -1,7 +1,7 @@
# hooks-field-add-buf-len
Emplacing a new field into STObject by calling [sto_emplace](https://xrpl-hooks.readme.io/v2.0/reference/sto_emplace) requires enough space to serialize the new STObject into; the API also limits sizes of the old object and field.
Emplacing a new field into STObject by calling [sto_emplace](https://xrpl-hooks.readme.io/reference/sto_emplace) requires enough space to serialize the new STObject into; the API also limits sizes of the old object and field.
This check warns about insufficient output buffer space as well as too-large values of the inputs in calls to `sto_emplace` (if they're specified by constants - variable parameters are ignored).
[Read more](https://xrpl-hooks.readme.io/v2.0/docs/serialized-objects)
[Read more](https://xrpl-hooks.readme.io/docs/serialized-objects)

View File

@@ -1,7 +1,7 @@
# hooks-field-buf-len
Hook API [sto_subfield](https://xrpl-hooks.readme.io/v2.0/reference/sto_subfield) requires non-empty input buffer.
Hook API [sto_subfield](https://xrpl-hooks.readme.io/reference/sto_subfield) requires non-empty input buffer.
This check warns about empty input in calls to `sto_subfield` (if it's specified by a constant - variable parameter is ignored).
[Read more](https://xrpl-hooks.readme.io/v2.0/docs/serialized-objects)
[Read more](https://xrpl-hooks.readme.io/docs/serialized-objects)

View File

@@ -1,7 +1,7 @@
# hooks-field-del-buf-len
Erasing a field from STObject by calling [sto_erase](https://xrpl-hooks.readme.io/v2.0/reference/sto_erase) requires enough space to serialize the new STObject into; the API also limits size of the old object.
Erasing a field from STObject by calling [sto_erase](https://xrpl-hooks.readme.io/reference/sto_erase) requires enough space to serialize the new STObject into; the API also limits size of the old object.
This check warns about insufficient output buffer space as well as too-large value of the input STObject in calls to `sto_erase` (if they're specified by constants - variable parameters are ignored).
[Read more](https://xrpl-hooks.readme.io/v2.0/docs/serialized-objects)
[Read more](https://xrpl-hooks.readme.io/docs/serialized-objects)

View File

@@ -1,8 +1,8 @@
# hooks-float-arith-pure
Hooks can compute floating-point values in XFL format by calling functions [float_multiply](https://xrpl-hooks.readme.io/v2.0/reference/float_multiply), [float_mulratio](https://xrpl-hooks.readme.io/v2.0/reference/float_mulratio), [float_negate](https://xrpl-hooks.readme.io/v2.0/reference/float_negate), [float_sum](https://xrpl-hooks.readme.io/v2.0/reference/float_sum), [float_invert](https://xrpl-hooks.readme.io/v2.0/reference/float_invert) and [float_divide](https://xrpl-hooks.readme.io/v2.0/reference/float_divide) and access their constituent parts by calling [float_exponent](https://xrpl-hooks.readme.io/v2.0/reference/float_exponent), [float_mantissa](https://xrpl-hooks.readme.io/v2.0/reference/float_mantissa) and [float_sign](https://xrpl-hooks.readme.io/v2.0/reference/float_sign). If the inputs of the computation never change, a more efficient way to do this is to precompute it.
Hooks can compute floating-point values in XFL format by calling functions [float_multiply](https://xrpl-hooks.readme.io/reference/float_multiply), [float_mulratio](https://xrpl-hooks.readme.io/reference/float_mulratio), [float_negate](https://xrpl-hooks.readme.io/reference/float_negate), [float_sum](https://xrpl-hooks.readme.io/reference/float_sum), [float_invert](https://xrpl-hooks.readme.io/reference/float_invert) and [float_divide](https://xrpl-hooks.readme.io/reference/float_divide) and access their constituent parts by calling [float_exponent](https://xrpl-hooks.readme.io/reference/float_exponent), [float_mantissa](https://xrpl-hooks.readme.io/reference/float_mantissa) and [float_sign](https://xrpl-hooks.readme.io/reference/float_sign). If the inputs of the computation never change, a more efficient way to do this is to precompute it.
This check warns about calls of the aforementioned functions with constant inputs and in simple cases proposes to add a tracing statement showing the computed value (so that the user can use it to replace the call). It also checks that the divisor passed to `float_divide`, `float_mulratio` and `float_invert` is not 0 (if it's specified by a constant - variable parameters are ignored).
[Read more](https://xrpl-hooks.readme.io/v2.0/docs/floating-point-numbers-xfl)
[Read more](https://xrpl-hooks.readme.io/docs/floating-point-numbers-xfl)

View File

@@ -1,7 +1,7 @@
# hooks-float-compare-pure
Hooks can compare floating-point values in XFL format by calling the [float_compare](https://xrpl-hooks.readme.io/v2.0/reference/float_compare) function. If the inputs of the comparison never change, its result is fixed and the function need not be called.
Hooks can compare floating-point values in XFL format by calling the [float_compare](https://xrpl-hooks.readme.io/reference/float_compare) function. If the inputs of the comparison never change, its result is fixed and the function need not be called.
This check warns about calls of `float_compare` with constant inputs as well as invalid values of the comparison mode parameter (if it's specified by a constant - variable parameter is ignored).
[Read more](https://xrpl-hooks.readme.io/v2.0/docs/floating-point-numbers-xfl)
[Read more](https://xrpl-hooks.readme.io/docs/floating-point-numbers-xfl)

View File

@@ -1,7 +1,7 @@
# hooks-float-int-pure
Hooks can convert floating-point values in XFL format to integers by calling the [float_int](https://xrpl-hooks.readme.io/v2.0/reference/float_int) function. If the inputs of this function never change, a more efficient way to do this is to precompute the integer value.
Hooks can convert floating-point values in XFL format to integers by calling the [float_int](https://xrpl-hooks.readme.io/reference/float_int) function. If the inputs of this function never change, a more efficient way to do this is to precompute the integer value.
This check warns about calls of `float_int` with constant inputs as well as invalid values of the decimal places parameter (if it's specified by a constant - variable parameter is ignored).
[Read more](https://xrpl-hooks.readme.io/v2.0/docs/floating-point-numbers-xfl)
[Read more](https://xrpl-hooks.readme.io/docs/floating-point-numbers-xfl)

View File

@@ -1,7 +1,7 @@
# hooks-float-manip-pure
Hooks can directly manipulate floating-point values in XFL format by calling functions [float_exponent_set](https://xrpl-hooks.readme.io/v2.0/reference/float_exponent_set), [float_mantissa_set](https://xrpl-hooks.readme.io/v2.0/reference/float_mantissa_set) and [float_sign_set](https://xrpl-hooks.readme.io/v2.0/reference/float_sign_set). If the inputs of the update never change, a more efficient way to do this is to precompute it.
Hooks can directly manipulate floating-point values in XFL format by calling functions [float_exponent_set](https://xrpl-hooks.readme.io/reference/float_exponent_set), [float_mantissa_set](https://xrpl-hooks.readme.io/reference/float_mantissa_set) and [float_sign_set](https://xrpl-hooks.readme.io/reference/float_sign_set). If the inputs of the update never change, a more efficient way to do this is to precompute it.
This check warns about calls of the aforementioned functions with constant inputs and in simple cases proposes to add a tracing statement showing the computed value (so that the user can use it to replace the call). It also checks documented bounds of the second parameter of these functions (if it's specified by a constant - variable parameter is ignored).
[Read more](https://xrpl-hooks.readme.io/v2.0/docs/floating-point-numbers-xfl)
[Read more](https://xrpl-hooks.readme.io/docs/floating-point-numbers-xfl)

View File

@@ -1,5 +1,5 @@
# hooks-float-one-pure
Hooks can obtain XFL enclosing number 1 by calling the [float_one](https://xrpl-hooks.readme.io/v2.0/reference/float_one) function. Since the number never changes, a more efficient way is to use its precomputed value.
Hooks can obtain XFL enclosing number 1 by calling the [float_one](https://xrpl-hooks.readme.io/reference/float_one) function. Since the number never changes, a more efficient way is to use its precomputed value.
[Read more](https://xrpl-hooks.readme.io/v2.0/docs/floating-point-numbers-xfl)
[Read more](https://xrpl-hooks.readme.io/docs/floating-point-numbers-xfl)

View File

@@ -1,7 +1,7 @@
# hooks-float-pure
Hooks can use floating-point values in XFL format, creating them from mantissa and exponent by calling the [float_set](https://xrpl-hooks.readme.io/v2.0/reference/float_set) function. If the mantissa and exponent never change, a more efficient way to do this is to precompute the floating-point value.
Hooks can use floating-point values in XFL format, creating them from mantissa and exponent by calling the [float_set](https://xrpl-hooks.readme.io/reference/float_set) function. If the mantissa and exponent never change, a more efficient way to do this is to precompute the floating-point value.
This check warns about calls of `float_set` with constant inputs and proposes to add a tracing statement showing the computed value (so that the user can use it to replace the call). In the special case of 0 mantissa and 0 exponent ("canonical 0"), a replacement value of 0 is proposed directly, with no need to trace it.
[Read more](https://xrpl-hooks.readme.io/v2.0/docs/floating-point-numbers-xfl)
[Read more](https://xrpl-hooks.readme.io/docs/floating-point-numbers-xfl)

View File

@@ -0,0 +1,6 @@
# hooks-guard-call-non-const
Only compile-time constants can be used as an argument in loop GUARD call. This check warns if a non compile-time constant is used.
It also checks whether a compile-time constant is used as a first argument of `_g()` call and whether it is a unique value. If not - it warns.
[Read more](https://xrpl-hooks.readme.io/docs/loops-and-guarding)

View File

@@ -1,5 +1,5 @@
# hooks-guard-called
Every hook needs to import the guard function [_g](https://xrpl-hooks.readme.io/v2.0/docs/loops-and-guarding#the-guard-function) and use it at least once.
Every hook needs to import the guard function [_g](https://xrpl-hooks.readme.io/docs/loops-and-guarding#the-guard-function) and use it at least once.
[Read more](https://xrpl-hooks.readme.io/v2.0/docs/loops-and-guarding)
[Read more](https://xrpl-hooks.readme.io/docs/loops-and-guarding)

View File

@@ -1,14 +1,35 @@
# hooks-guard-in-for
A guard is a marker that must be placed in your code at the top of each loop. Consider the following for-loop in C:
Consider the following for-loop in C:
```c
#define GUARD(maxiter) _g(__LINE__, (maxiter)+1)
for (int i = 0; GUARD(3), i < 3; ++i)
#define GUARD(maxiter) _g(__LINE__, (maxiter)+1)
for (int i = 0; GUARD(3), i < 3; ++i)
```
<BR/>
This is the only way to satisfy the guard rule when using a for-loop in C.
To satisfy the guard rule when using a for-loop in C guard should be
placed either in the condition part of the loop, or as a first call in loop body, e.g.
[Read more](https://xrpl-hooks.readme.io/v2.0/docs/loops-and-guarding)
```c
for(int i = 0; i < 3; ++i) {
GUARD(3);
}
```
In case of nested loops, the guard limit value should be
multiplied by a number of iterations in each loop, e.g.
```c
for(int i = 0; GUARD(3), i < 3; ++i) {
for (int j = 0; GUARD(17), j < 5; ++j)
}
```
```
(most descendant loop iterations + 1) * (each parent loops iterations) - 1
```
This check will warn if the GUARD call is missing and also it will propose a GUARD value based on the for loop initial value,
the increment and loop condition.
[Read more](https://xrpl-hooks.readme.io/docs/loops-and-guarding)

View File

@@ -11,4 +11,4 @@ Like for loops, while loops must have a guard in their condition:
```
<BR/>
[Read more](https://xrpl-hooks.readme.io/v2.0/docs/loops-and-guarding)
[Read more](https://xrpl-hooks.readme.io/docs/loops-and-guarding)

View File

@@ -1,5 +1,5 @@
# hooks-hash-buf-len
Functions [util_sha512h](https://xrpl-hooks.readme.io/v2.0/reference/util_sha512h), [hook_hash](https://xrpl-hooks.readme.io/v2.0/reference/hook_hash), [ledger_last_hash](https://xrpl-hooks.readme.io/v2.0/reference/ledger_last_hash), [etxn_nonce](https://xrpl-hooks.readme.io/v2.0/reference/etxn_nonce) and [ledger_nonce](https://xrpl-hooks.readme.io/v2.0/reference/ledger_nonce) have fixed-size hash output.
Functions [util_sha512h](https://xrpl-hooks.readme.io/reference/util_sha512h), [hook_hash](https://xrpl-hooks.readme.io/reference/hook_hash), [ledger_last_hash](https://xrpl-hooks.readme.io/reference/ledger_last_hash), [etxn_nonce](https://xrpl-hooks.readme.io/reference/etxn_nonce) and [ledger_nonce](https://xrpl-hooks.readme.io/reference/ledger_nonce) have fixed-size hash output.
This check warns about too-small size of their output buffer (if it's specified by a constant - variable parameter is ignored).

View File

@@ -1,7 +1,7 @@
# hooks-keylet-buf-len
Computing a ripple keylet by calling [util_keylet](https://xrpl-hooks.readme.io/v2.0/reference/util_keylet) requires valid parameters dependent on the keylet type.
Computing a ripple keylet by calling [util_keylet](https://xrpl-hooks.readme.io/reference/util_keylet) requires valid parameters dependent on the keylet type.
This check does not fully parse these parameters, but warns about invalid keylet type as well as buffer sizes that cannot be valid (if they're specified by constants - variable parameters are ignored).
[Read more](https://xrpl-hooks.readme.io/v2.0/docs/slots-and-keylets)
[Read more](https://xrpl-hooks.readme.io/docs/slots-and-keylets)

View File

@@ -1,7 +1,7 @@
# hooks-param-buf-len
Function [hook_param](https://xrpl-hooks.readme.io/v2.0/reference/hook_param) expects a limited-length name input and produces fixed-size value output.
Function [hook_param](https://xrpl-hooks.readme.io/reference/hook_param) expects a limited-length name input and produces fixed-size value output.
This check warns about invalid sizes of input and output buffers (if they're specified by constants - variable parameters are ignored).
[Read more](https://xrpl-hooks.readme.io/v2.0/docs/parameters)
[Read more](https://xrpl-hooks.readme.io/docs/parameters)

View File

@@ -1,7 +1,7 @@
# hooks-param-set-buf-len
Function [hook_param_set](https://xrpl-hooks.readme.io/v2.0/reference/hook_param_set) expects limited-length name, fixed-length hash and limited-length value inputs.
Function [hook_param_set](https://xrpl-hooks.readme.io/reference/hook_param_set) expects limited-length name, fixed-length hash and limited-length value inputs.
This check warns about invalid sizes of input buffers (if they're specified by constants - variable parameters are ignored).
[Read more](https://xrpl-hooks.readme.io/v2.0/docs/parameters)
[Read more](https://xrpl-hooks.readme.io/docs/parameters)

View File

@@ -1,5 +1,5 @@
# hooks-raddr-conv-buf-len
Hook API [util_accid](https://xrpl-hooks.readme.io/v2.0/reference/util_accid) has upper limit on the length of its input (because it expects it to be a raddr) and fixed-size account ID output.
Hook API [util_accid](https://xrpl-hooks.readme.io/reference/util_accid) has upper limit on the length of its input (because it expects it to be a raddr) and fixed-size account ID output.
This check warns about invalid sizes of input and output parameters (if they're specified by constants - variable parameters are ignored).

View File

@@ -1,5 +1,5 @@
# hooks-raddr-conv-pure
Hooks identify accounts by the 20 byte account ID, which can be converted from a raddr using the [util_accid](https://xrpl-hooks.readme.io/v2.0/reference/util_accid) function. If the raddr never changes, a more efficient way to do this is precompute the account-id from the raddr.
Hooks identify accounts by the 20 byte account ID, which can be converted from a raddr using the [util_accid](https://xrpl-hooks.readme.io/reference/util_accid) function. If the raddr never changes, a more efficient way to do this is precompute the account-id from the raddr.
This check warns about calls of `util_accid` with constant input and proposes to add a tracing statement showing the computed value (so that the user can use it to replace the call).

View File

@@ -1,7 +1,7 @@
# hooks-reserve-limit
Hook API [etxn_reserve](https://xrpl-hooks.readme.io/v2.0/reference/etxn_reserve) takes a parameter specifying the number of transactions intended to emit from the calling hook. Value of this parameter is limited, and the function fails if the limit is exceeded.
Hook API [etxn_reserve](https://xrpl-hooks.readme.io/reference/etxn_reserve) takes a parameter specifying the number of transactions intended to emit from the calling hook. Value of this parameter is limited, and the function fails if the limit is exceeded.
This check warns about too-large values of the number of reserved transactions (if they're specified by a constant - variable parameter is ignored).
[Read more](https://xrpl-hooks.readme.io/v2.0/docs/emitted-transactions)
[Read more](https://xrpl-hooks.readme.io/docs/emitted-transactions)

View File

@@ -1,5 +1,5 @@
# hooks-skip-hash-buf-len
Function [hook_skip](https://xrpl-hooks.readme.io/v2.0/reference/hook_skip) has fixed-size canonical hash input.
Function [hook_skip](https://xrpl-hooks.readme.io/reference/hook_skip) has fixed-size canonical hash input.
This check warns about invalid size of its input buffer (if it's specified by a constant - variable parameter is ignored).

View File

@@ -1,7 +1,7 @@
# hooks-slot-hash-buf-len
Function [slot_id](https://xrpl-hooks.readme.io/v2.0/reference/slot_id) has fixed-size canonical hash output.
Function [slot_id](https://xrpl-hooks.readme.io/reference/slot_id) has fixed-size canonical hash output.
This check warns about too-small size of its output buffer as well as invalid values of the slot number parameter (if they're specified by constants - variable parameters are ignored).
[Read more](https://xrpl-hooks.readme.io/v2.0/docs/slots-and-keylets)
[Read more](https://xrpl-hooks.readme.io/docs/slots-and-keylets)

View File

@@ -1,7 +1,7 @@
# hooks-slot-keylet-buf-len
Function [slot_set](https://xrpl-hooks.readme.io/v2.0/reference/slot_set) has structured keylet input.
Function [slot_set](https://xrpl-hooks.readme.io/reference/slot_set) has structured keylet input.
This check does not parse the input, but warns about its sizes that cannot be valid as well as invalid values of the slot number parameter (if they're specified by constants - variable parameters are ignored).
[Read more](https://xrpl-hooks.readme.io/v2.0/docs/slots-and-keylets)
[Read more](https://xrpl-hooks.readme.io/docs/slots-and-keylets)

View File

@@ -1,7 +1,7 @@
# hooks-slot-limit
Hook APIs [slot](https://xrpl-hooks.readme.io/v2.0/reference/slot), [slot_count](https://xrpl-hooks.readme.io/v2.0/reference/slot_count), [slot_clear](https://xrpl-hooks.readme.io/v2.0/reference/slot_clear), [slot_size](https://xrpl-hooks.readme.io/v2.0/reference/slot_size), [slot_float](https://xrpl-hooks.readme.io/v2.0/reference/slot_float) and [trace_slot](https://xrpl-hooks.readme.io/v2.0/reference/trace_slot) take a parameter specifying the accessed slot number. Value of this parameter is limited, and the functions fail if the limit is exceeded.
Hook APIs [slot](https://xrpl-hooks.readme.io/reference/slot), [slot_count](https://xrpl-hooks.readme.io/reference/slot_count), [slot_clear](https://xrpl-hooks.readme.io/reference/slot_clear), [slot_size](https://xrpl-hooks.readme.io/reference/slot_size), [slot_float](https://xrpl-hooks.readme.io/reference/slot_float) and [trace_slot](https://xrpl-hooks.readme.io/reference/trace_slot) take a parameter specifying the accessed slot number. Value of this parameter is limited, and the functions fail if the limit is exceeded.
This check warns about too-large values of the slot number (if it's specified by a constant - variable parameter is ignored).
[Read more](https://xrpl-hooks.readme.io/v2.0/docs/slots-and-keylets)
[Read more](https://xrpl-hooks.readme.io/docs/slots-and-keylets)

View File

@@ -1,7 +1,7 @@
# hooks-slot-sub-limit
Hook APIs [slot_subarray](https://xrpl-hooks.readme.io/v2.0/reference/slot_subarray) and [slot_subfield](https://xrpl-hooks.readme.io/v2.0/reference/slot_subfield) take parameters specifying parent and child slot numbers. Values of these parameters are limited, and the functions fail if the limit is exceeded.
Hook APIs [slot_subarray](https://xrpl-hooks.readme.io/reference/slot_subarray) and [slot_subfield](https://xrpl-hooks.readme.io/reference/slot_subfield) take parameters specifying parent and child slot numbers. Values of these parameters are limited, and the functions fail if the limit is exceeded.
This check warns about too-large values of the slot numbers (if they're specified by a constant - variable parameters are ignored).
[Read more](https://xrpl-hooks.readme.io/v2.0/docs/slots-and-keylets)
[Read more](https://xrpl-hooks.readme.io/docs/slots-and-keylets)

View File

@@ -1,7 +1,7 @@
# hooks-slot-type-limit
Hook API [slot_type](https://xrpl-hooks.readme.io/v2.0/reference/slot_type) takes a parameter specifying the accessed slot number. Value of this parameter is limited, and the function fails if the limit is exceeded.
Hook API [slot_type](https://xrpl-hooks.readme.io/reference/slot_type) takes a parameter specifying the accessed slot number. Value of this parameter is limited, and the function fails if the limit is exceeded.
This check warns about too-large values of the slot number as well as invalid values of the flags parameter (if they're specified by constants - variable parameters are ignored).
[Read more](https://xrpl-hooks.readme.io/v2.0/docs/slots-and-keylets)
[Read more](https://xrpl-hooks.readme.io/docs/slots-and-keylets)

View File

@@ -1,7 +1,7 @@
# hooks-state-buf-len
Functions [state](https://xrpl-hooks.readme.io/v2.0/reference/state) and [state_set](https://xrpl-hooks.readme.io/v2.0/reference/state_set) accept fixed-size Hook State key.
Functions [state](https://xrpl-hooks.readme.io/reference/state) and [state_set](https://xrpl-hooks.readme.io/reference/state_set) accept fixed-size Hook State key.
This check warns about invalid size of its input buffer (if it's specified by a constant - variable parameter is ignored).
[Read more](https://xrpl-hooks.readme.io/v2.0/docs/state-management)
[Read more](https://xrpl-hooks.readme.io/docs/state-management)

View File

@@ -1,5 +1,5 @@
# hooks-transaction-hash-buf-len
Function [otxn_id](https://xrpl-hooks.readme.io/v2.0/reference/otxn_id) has fixed-size canonical hash output.
Function [otxn_id](https://xrpl-hooks.readme.io/reference/otxn_id) has fixed-size canonical hash output.
This check warns about too-small size of its output buffer (if it's specified by a constant - variable parameter is ignored).

View File

@@ -1,7 +1,7 @@
# hooks-transaction-slot-limit
Function [otxn_slot](https://xrpl-hooks.readme.io/v2.0/reference/otxn_slot) takes a parameter specifying the accessed slot number. Value of this parameter is limited, and the function fails if the limit is exceeded.
Function [otxn_slot](https://xrpl-hooks.readme.io/reference/otxn_slot) takes a parameter specifying the accessed slot number. Value of this parameter is limited, and the function fails if the limit is exceeded.
This check warns about too-large values of the slot number (if it's specified by a constant - variable parameter is ignored).
[Read more](https://xrpl-hooks.readme.io/v2.0/docs/slots-and-keylets)
[Read more](https://xrpl-hooks.readme.io/docs/slots-and-keylets)

View File

@@ -1,7 +1,7 @@
# hooks-trivial-cbak
A Hook may implement and export a [cbak](https://xrpl-hooks.readme.io/v2.0/reference/cbak) function.
A Hook may implement and export a [cbak](https://xrpl-hooks.readme.io/reference/cbak) function.
But the function is optional, and defining it so that it doesn't do anything besides returning a constant value is unnecessary (except for some debugging scenarios) and just increases the hook size. This check warns about such implementations.
[Read more](https://xrpl-hooks.readme.io/v2.0/docs/compiling-hooks)
[Read more](https://xrpl-hooks.readme.io/docs/compiling-hooks)

View File

@@ -1,7 +1,7 @@
# hooks-validate-buf-len
Hook API [sto_validate](https://xrpl-hooks.readme.io/v2.0/reference/sto_validate) requires non-empty input buffer.
Hook API [sto_validate](https://xrpl-hooks.readme.io/reference/sto_validate) requires non-empty input buffer.
This check warns about empty input in calls to `sto_validate` (if it's specified by a constant - variable parameter is ignored).
[Read more](https://xrpl-hooks.readme.io/v2.0/docs/serialized-objects)
[Read more](https://xrpl-hooks.readme.io/docs/serialized-objects)

View File

@@ -1,5 +1,5 @@
# hooks-verify-buf-len
Verifying a cryptographic signature by calling [util_verify](https://xrpl-hooks.readme.io/v2.0/reference/util_verify) requires valid public key & data signature.
Verifying a cryptographic signature by calling [util_verify](https://xrpl-hooks.readme.io/reference/util_verify) requires valid public key & data signature.
This check does not fully parse these parameters, but warns about their sizes that cannot be valid (if they're specified by constants - variable parameters are ignored).

1033
yarn.lock

File diff suppressed because it is too large Load Diff