Merge branch 'main' into feat/ts-support

This commit is contained in:
muzam1l
2023-02-20 19:52:33 +05:30
22 changed files with 3997 additions and 1925 deletions

View File

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

4
.gitignore vendored
View File

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

View File

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

View File

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

View File

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

View File

@@ -245,5 +245,14 @@
} }
}, },
"Sequence": 12 "Sequence": 12
},
{
"TransactionType": "Invoke",
"Fee": "12"
},
{
"TransactionType": "UriToken",
"Fee": "12",
"URI": "697066733A2F2F516D614374444B5A4656767666756676626479346573745A626851483744586831364354707631686F776D424779"
} }
] ]

View File

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

View File

@@ -8,7 +8,8 @@
"start": "next start", "start": "next start",
"lint": "next lint", "lint": "next lint",
"format": "prettier --write .", "format": "prettier --write .",
"postinstall": "patch-package" "postinstall": "patch-package && yarn run postinstall-postinstall",
"postinstall-postinstall": "./node_modules/.bin/browserify -r ripple-binary-codec -r ripple-keypairs -r ripple-address-codec -r ripple-secret-codec -r ./node_modules/xrpl-accountlib/dist/index.js:xrpl-accountlib -o node_modules/xrpl-accountlib/dist/browser.hook-bundle.js"
}, },
"dependencies": { "dependencies": {
"@codingame/monaco-jsonrpc": "^0.3.1", "@codingame/monaco-jsonrpc": "^0.3.1",
@@ -66,8 +67,8 @@
"vscode-languageserver": "^7.0.0", "vscode-languageserver": "^7.0.0",
"vscode-uri": "^3.0.2", "vscode-uri": "^3.0.2",
"wabt": "^1.0.32", "wabt": "^1.0.32",
"xrpl-accountlib": "^1.5.2", "xrpl-accountlib": "^1.6.1",
"xrpl-client": "^1.9.4" "xrpl-client": "^2.0.2"
}, },
"devDependencies": { "devDependencies": {
"@types/dinero.js": "^1.9.0", "@types/dinero.js": "^1.9.0",
@@ -76,6 +77,7 @@
"@types/lodash.xor": "^4.5.6", "@types/lodash.xor": "^4.5.6",
"@types/pako": "^1.0.2", "@types/pako": "^1.0.2",
"@types/react": "17.0.31", "@types/react": "17.0.31",
"browserify": "^17.0.0",
"eslint": "7.32.0", "eslint": "7.32.0",
"eslint-config-next": "^13.1.1", "eslint-config-next": "^13.1.1",
"raw-loader": "^4.0.2", "raw-loader": "^4.0.2",

File diff suppressed because it is too large Load Diff

5
raw-loader.d.ts vendored
View File

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

View File

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

View File

@@ -1,66 +0,0 @@
import { derive, sign } from 'xrpl-accountlib'
import state from '..'
import type { IAccount } from '..'
interface TransactionOptions {
TransactionType: string
Account?: string
Fee?: string
Destination?: string
[index: string]: any
}
interface OtherOptions {
logPrefix?: string
}
export const sendTransaction = async (
account: IAccount,
txOptions: TransactionOptions,
options?: OtherOptions
) => {
if (!state.client) throw Error('XRPL client not initalized')
const { Fee = '1000', ...opts } = txOptions
const tx: TransactionOptions = {
Account: account.address,
Sequence: account.sequence,
Fee, // TODO auto-fillable default
...opts
}
const { logPrefix = '' } = options || {}
try {
const signedAccount = derive.familySeed(account.secret)
const { signedTransaction } = sign(tx, signedAccount)
const response = await state.client.send({
command: 'submit',
tx_blob: signedTransaction
})
if (response.engine_result === 'tesSUCCESS') {
state.transactionLogs.push({
type: 'success',
message: `${logPrefix}[${response.engine_result}] ${response.engine_result_message}`
})
} else {
state.transactionLogs.push({
type: 'error',
message: `${logPrefix}[${response.error || response.engine_result}] ${
response.error_exception || response.engine_result_message
}`
})
}
const currAcc = state.accounts.find(acc => acc.address === account.address)
if (currAcc && response.account_sequence_next) {
currAcc.sequence = response.account_sequence_next
}
} catch (err) {
console.error(err)
state.transactionLogs.push({
type: 'error',
message:
err instanceof Error
? `${logPrefix}Error: ${err.message}`
: `${logPrefix}Something went wrong, try again later`
})
}
}

View File

@@ -4,6 +4,7 @@ import state from '..'
import type { IAccount } from '..' import type { IAccount } from '..'
import ResultLink from '../../components/ResultLink' import ResultLink from '../../components/ResultLink'
import { ref } from 'valtio' import { ref } from 'valtio'
import { xrplSend } from './xrpl-client'
interface TransactionOptions { interface TransactionOptions {
TransactionType: string TransactionType: string
@@ -21,20 +22,19 @@ export const sendTransaction = async (
txOptions: TransactionOptions, txOptions: TransactionOptions,
options?: OtherOptions options?: OtherOptions
) => { ) => {
if (!state.client) throw Error('XRPL client not initalized')
const { Fee = '1000', ...opts } = txOptions const { Fee = '1000', ...opts } = txOptions
const tx: TransactionOptions = { const tx: TransactionOptions = {
Account: account.address, Account: account.address,
Sequence: account.sequence, Sequence: account.sequence,
Fee, // TODO auto-fillable default Fee,
NetworkID: process.env.NEXT_PUBLIC_NETWORK_ID,
...opts ...opts
} }
const { logPrefix = '' } = options || {} const { logPrefix = '' } = options || {}
try { try {
const signedAccount = derive.familySeed(account.secret) const signedAccount = derive.familySeed(account.secret)
const { signedTransaction } = sign(tx, signedAccount) const { signedTransaction } = sign(tx, signedAccount)
const response = await state.client.send({ const response = await xrplSend({
command: 'submit', command: 'submit',
tx_blob: signedTransaction tx_blob: signedTransaction
}) })

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

View File

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

View File

@@ -1,6 +1,7 @@
import toast from 'react-hot-toast' import toast from 'react-hot-toast'
import { derive, sign } from 'xrpl-accountlib' import { derive, sign } from 'xrpl-accountlib'
import state, { IAccount } from '../state' import { IAccount } from '../state'
import { xrplSend } from '../state/actions/xrpl-client'
const estimateFee = async ( const estimateFee = async (
tx: Record<string, unknown>, tx: Record<string, unknown>,
@@ -22,7 +23,7 @@ const estimateFee = async (
const keypair = derive.familySeed(account.secret) const keypair = derive.familySeed(account.secret)
const { signedTransaction } = sign(copyTx, keypair) const { signedTransaction } = sign(copyTx, keypair)
const res = await state.client?.send({ command: 'fee', tx_blob: signedTransaction }) const res = await xrplSend({ command: 'fee', tx_blob: signedTransaction })
if (res && res.drops) { if (res && res.drops) {
return res.drops return res.drops
} }

View File

@@ -29,17 +29,15 @@ export const tts = {
export type TTS = typeof tts export type TTS = typeof tts
const calculateHookOn = (arr: (keyof TTS)[]) => { const calculateHookOn = (arr: (keyof TTS)[]) => {
let start = '0x000000003e3ff5bf' let s = '0x3e3ff5bf'
arr.forEach(n => { arr.forEach(n => {
let v = BigInt(start) let v = BigInt(s)
v ^= BigInt(1) << BigInt(tts[n as keyof TTS]) v ^= BigInt(1) << BigInt(tts[n])
let s = v.toString(16) s = "0x" + v.toString(16)
let l = s.length
if (l < 16) s = '0'.repeat(16 - l) + s
s = '0x' + s
start = s
}) })
return start.substring(2) s = s.replace('0x', '')
s = s.padStart(64, '0')
return s
} }
export default calculateHookOn export default calculateHookOn

File diff suppressed because one or more lines are too long

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

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/v2.0/docs/loops-and-guarding)

View File

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

906
yarn.lock

File diff suppressed because it is too large Load Diff