Run prettier through everything.
This commit is contained in:
@@ -1,25 +1,24 @@
|
||||
|
||||
import toast from "react-hot-toast";
|
||||
import state, { FaucetAccountRes } from '../index';
|
||||
import toast from 'react-hot-toast'
|
||||
import state, { FaucetAccountRes } from '../index'
|
||||
|
||||
export const names = [
|
||||
"Alice",
|
||||
"Bob",
|
||||
"Carol",
|
||||
"Carlos",
|
||||
"Charlie",
|
||||
"Dan",
|
||||
"Dave",
|
||||
"David",
|
||||
"Faythe",
|
||||
"Frank",
|
||||
"Grace",
|
||||
"Heidi",
|
||||
"Judy",
|
||||
"Olive",
|
||||
"Peggy",
|
||||
"Walter",
|
||||
];
|
||||
'Alice',
|
||||
'Bob',
|
||||
'Carol',
|
||||
'Carlos',
|
||||
'Charlie',
|
||||
'Dan',
|
||||
'Dave',
|
||||
'David',
|
||||
'Faythe',
|
||||
'Frank',
|
||||
'Grace',
|
||||
'Heidi',
|
||||
'Judy',
|
||||
'Olive',
|
||||
'Peggy',
|
||||
'Walter'
|
||||
]
|
||||
|
||||
/* This function adds faucet account to application global state.
|
||||
* It calls the /api/faucet endpoint which in send a HTTP POST to
|
||||
@@ -30,22 +29,22 @@ export const names = [
|
||||
export const addFaucetAccount = async (name?: string, showToast: boolean = false) => {
|
||||
if (typeof window === undefined) return
|
||||
|
||||
const toastId = showToast ? toast.loading("Creating account") : "";
|
||||
const toastId = showToast ? toast.loading('Creating account') : ''
|
||||
const res = await fetch(`${window.location.origin}/api/faucet`, {
|
||||
method: "POST",
|
||||
});
|
||||
const json: FaucetAccountRes | { error: string } = await res.json();
|
||||
if ("error" in json) {
|
||||
method: 'POST'
|
||||
})
|
||||
const json: FaucetAccountRes | { error: string } = await res.json()
|
||||
if ('error' in json) {
|
||||
if (showToast) {
|
||||
return toast.error(json.error, { id: toastId });
|
||||
return toast.error(json.error, { id: toastId })
|
||||
} else {
|
||||
return;
|
||||
return
|
||||
}
|
||||
} else {
|
||||
if (showToast) {
|
||||
toast.success("New account created", { id: toastId });
|
||||
toast.success('New account created', { id: toastId })
|
||||
}
|
||||
const currNames = state.accounts.map(acc => acc.name);
|
||||
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(),
|
||||
@@ -55,36 +54,35 @@ export const addFaucetAccount = async (name?: string, showToast: boolean = false
|
||||
hooks: [],
|
||||
isLoading: false,
|
||||
version: '2'
|
||||
});
|
||||
})
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
// fetch initial faucets
|
||||
(async function fetchFaucets() {
|
||||
;(async function fetchFaucets() {
|
||||
if (typeof window !== 'undefined') {
|
||||
if (state.accounts.length === 0) {
|
||||
await addFaucetAccount();
|
||||
await addFaucetAccount()
|
||||
// setTimeout(() => {
|
||||
// addFaucetAccount();
|
||||
// }, 10000);
|
||||
}
|
||||
}
|
||||
})();
|
||||
})()
|
||||
|
||||
export const addFunds = async (address: string) => {
|
||||
const toastId = toast.loading("Requesting funds");
|
||||
const toastId = toast.loading('Requesting funds')
|
||||
const res = await fetch(`${window.location.origin}/api/faucet?account=${address}`, {
|
||||
method: "POST",
|
||||
});
|
||||
const json: FaucetAccountRes | { error: string } = await res.json();
|
||||
if ("error" in json) {
|
||||
return toast.error(json.error, { id: toastId });
|
||||
method: 'POST'
|
||||
})
|
||||
const json: FaucetAccountRes | { error: string } = await res.json()
|
||||
if ('error' in json) {
|
||||
return toast.error(json.error, { id: toastId })
|
||||
} else {
|
||||
toast.success(`Funds added (${json.xrp} XRP)`, { id: toastId });
|
||||
const currAccount = state.accounts.find(acc => acc.address === address);
|
||||
toast.success(`Funds added (${json.xrp} XRP)`, { id: toastId })
|
||||
const currAccount = state.accounts.find(acc => acc.address === address)
|
||||
if (currAccount) {
|
||||
currAccount.xrp = (Number(currAccount.xrp) + (json.xrp * 1000000)).toString();
|
||||
currAccount.xrp = (Number(currAccount.xrp) + json.xrp * 1000000).toString()
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,30 +1,30 @@
|
||||
import toast from "react-hot-toast";
|
||||
import Router from 'next/router';
|
||||
import toast from 'react-hot-toast'
|
||||
import Router from 'next/router'
|
||||
|
||||
import state from "../index";
|
||||
import { saveFile } from "./saveFile";
|
||||
import { decodeBinary } from "../../utils/decodeBinary";
|
||||
import { ref } from "valtio";
|
||||
import state from '../index'
|
||||
import { saveFile } from './saveFile'
|
||||
import { decodeBinary } from '../../utils/decodeBinary'
|
||||
import { ref } from 'valtio'
|
||||
|
||||
/* compileCode sends the code of the active file to compile endpoint
|
||||
* If all goes well you will get base64 encoded wasm file back with
|
||||
* some extra logging information if we can provide it. This function
|
||||
* some extra logging information if we can provide it. This function
|
||||
* also decodes the returned wasm and creates human readable WAT file
|
||||
* out of it and store both in global state.
|
||||
*/
|
||||
export const compileCode = async (activeId: number) => {
|
||||
// Save the file to global state
|
||||
saveFile(false, activeId);
|
||||
saveFile(false, activeId)
|
||||
if (!process.env.NEXT_PUBLIC_COMPILE_API_ENDPOINT) {
|
||||
throw Error("Missing env!");
|
||||
throw Error('Missing env!')
|
||||
}
|
||||
// Bail out if we're already compiling
|
||||
if (state.compiling) {
|
||||
// if compiling is ongoing return // TODO Inform user about it.
|
||||
return;
|
||||
return
|
||||
}
|
||||
// Set loading state to true
|
||||
state.compiling = true;
|
||||
state.compiling = true
|
||||
state.logs = []
|
||||
const file = state.files[activeId]
|
||||
try {
|
||||
@@ -32,29 +32,29 @@ export const compileCode = async (activeId: number) => {
|
||||
let res: Response
|
||||
try {
|
||||
res = await fetch(process.env.NEXT_PUBLIC_COMPILE_API_ENDPOINT, {
|
||||
method: "POST",
|
||||
method: 'POST',
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
body: JSON.stringify({
|
||||
output: "wasm",
|
||||
output: 'wasm',
|
||||
compress: true,
|
||||
strip: state.compileOptions.strip,
|
||||
files: [
|
||||
{
|
||||
type: "c",
|
||||
type: 'c',
|
||||
options: state.compileOptions.optimizationLevel || '-O2',
|
||||
name: file.name,
|
||||
src: file.content,
|
||||
},
|
||||
],
|
||||
}),
|
||||
});
|
||||
src: file.content
|
||||
}
|
||||
]
|
||||
})
|
||||
})
|
||||
} catch (error) {
|
||||
throw Error("Something went wrong, check your network connection and try again!")
|
||||
throw Error('Something went wrong, check your network connection and try again!')
|
||||
}
|
||||
const json = await res.json();
|
||||
state.compiling = false;
|
||||
const json = await res.json()
|
||||
state.compiling = false
|
||||
if (!json.success) {
|
||||
const errors = [json.message]
|
||||
if (json.tasks && json.tasks.length > 0) {
|
||||
@@ -62,65 +62,63 @@ export const compileCode = async (activeId: number) => {
|
||||
if (!task.success) {
|
||||
errors.push(task?.console)
|
||||
}
|
||||
});
|
||||
})
|
||||
}
|
||||
throw errors
|
||||
}
|
||||
try {
|
||||
// Decode base64 encoded wasm that is coming back from the endpoint
|
||||
const bufferData = await decodeBinary(json.output);
|
||||
const bufferData = await decodeBinary(json.output)
|
||||
|
||||
// Import wabt from and create human readable version of wasm file and
|
||||
// put it into state
|
||||
const ww = (await import('wabt')).default()
|
||||
const myModule = ww.readWasm(new Uint8Array(bufferData), {
|
||||
readDebugNames: true,
|
||||
});
|
||||
myModule.applyNames();
|
||||
readDebugNames: true
|
||||
})
|
||||
myModule.applyNames()
|
||||
|
||||
const wast = myModule.toText({ foldExprs: false, inlineExport: false });
|
||||
const wast = myModule.toText({ foldExprs: false, inlineExport: false })
|
||||
|
||||
file.compiledContent = ref(bufferData);
|
||||
file.lastCompiled = new Date();
|
||||
file.compiledContent = ref(bufferData)
|
||||
file.lastCompiled = new Date()
|
||||
file.compiledValueSnapshot = file.content
|
||||
file.compiledWatContent = wast;
|
||||
file.compiledWatContent = wast
|
||||
} catch (error) {
|
||||
throw Error("Invalid compilation result produced, check your code for errors and try again!")
|
||||
throw Error('Invalid compilation result produced, check your code for errors and try again!')
|
||||
}
|
||||
|
||||
toast.success("Compiled successfully!", { position: "bottom-center" });
|
||||
toast.success('Compiled successfully!', { position: 'bottom-center' })
|
||||
state.logs.push({
|
||||
type: "success",
|
||||
type: 'success',
|
||||
message: `File ${state.files?.[activeId]?.name} compiled successfully. Ready to deploy.`,
|
||||
link: Router.asPath.replace("develop", "deploy"),
|
||||
linkText: "Go to deploy",
|
||||
});
|
||||
link: Router.asPath.replace('develop', 'deploy'),
|
||||
linkText: 'Go to deploy'
|
||||
})
|
||||
} catch (err) {
|
||||
console.log(err);
|
||||
console.log(err)
|
||||
|
||||
if (err instanceof Array && typeof err[0] === 'string') {
|
||||
err.forEach(message => {
|
||||
state.logs.push({
|
||||
type: "error",
|
||||
message,
|
||||
});
|
||||
type: 'error',
|
||||
message
|
||||
})
|
||||
})
|
||||
} else if (err instanceof Error) {
|
||||
state.logs.push({
|
||||
type: 'error',
|
||||
message: err.message
|
||||
})
|
||||
} else {
|
||||
state.logs.push({
|
||||
type: 'error',
|
||||
message: 'Something went wrong, come back later!'
|
||||
})
|
||||
}
|
||||
else if (err instanceof Error) {
|
||||
state.logs.push({
|
||||
type: "error",
|
||||
message: err.message,
|
||||
});
|
||||
}
|
||||
else {
|
||||
state.logs.push({
|
||||
type: "error",
|
||||
message: "Something went wrong, come back later!",
|
||||
});
|
||||
}
|
||||
|
||||
state.compiling = false;
|
||||
toast.error(`Error occurred while compiling!`, { position: "bottom-center" });
|
||||
state.compiling = false
|
||||
toast.error(`Error occurred while compiling!`, { position: 'bottom-center' })
|
||||
file.containsErrors = true
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@@ -1,24 +1,28 @@
|
||||
import state, { IFile } from '../index';
|
||||
import state, { IFile } from '../index'
|
||||
|
||||
const languageMapping = {
|
||||
'ts': 'typescript',
|
||||
'js': 'javascript',
|
||||
'md': 'markdown',
|
||||
'c': 'c',
|
||||
'h': 'c',
|
||||
'other': ''
|
||||
ts: 'typescript',
|
||||
js: 'javascript',
|
||||
md: 'markdown',
|
||||
c: 'c',
|
||||
h: 'c',
|
||||
other: ''
|
||||
} /* Initializes empty file to global state */
|
||||
export const createNewFile = (name: string) => {
|
||||
const tempName = name.split('.');
|
||||
const fileExt = tempName[tempName.length - 1] || 'other';
|
||||
const emptyFile: IFile = { name, language: languageMapping[fileExt as 'ts' | 'js' | 'md' | 'c' | 'h' | 'other'], content: "" };
|
||||
state.files.push(emptyFile);
|
||||
state.active = state.files.length - 1;
|
||||
};
|
||||
const tempName = name.split('.')
|
||||
const fileExt = tempName[tempName.length - 1] || 'other'
|
||||
const emptyFile: IFile = {
|
||||
name,
|
||||
language: languageMapping[fileExt as 'ts' | 'js' | 'md' | 'c' | 'h' | 'other'],
|
||||
content: ''
|
||||
}
|
||||
state.files.push(emptyFile)
|
||||
state.active = state.files.length - 1
|
||||
}
|
||||
|
||||
export const renameFile = (oldName: string, nwName: string) => {
|
||||
const file = state.files.find(file => file.name === oldName)
|
||||
if (!file) throw Error(`No file exists with name ${oldName}`)
|
||||
|
||||
file.name = nwName
|
||||
};
|
||||
}
|
||||
|
||||
@@ -1,24 +1,24 @@
|
||||
import state, { transactionsState } from '..';
|
||||
import state, { transactionsState } from '..'
|
||||
|
||||
export const deleteAccount = (addr?: string) => {
|
||||
if (!addr) return;
|
||||
const index = state.accounts.findIndex(acc => acc.address === addr);
|
||||
if (index === -1) return;
|
||||
state.accounts.splice(index, 1);
|
||||
if (!addr) return
|
||||
const index = state.accounts.findIndex(acc => acc.address === addr)
|
||||
if (index === -1) return
|
||||
state.accounts.splice(index, 1)
|
||||
|
||||
// update selected accounts
|
||||
transactionsState.transactions
|
||||
.filter(t => t.state.selectedAccount?.value === addr)
|
||||
.forEach(t => {
|
||||
const acc = t.state.selectedAccount;
|
||||
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;
|
||||
});
|
||||
};
|
||||
// update selected accounts
|
||||
transactionsState.transactions
|
||||
.filter(t => t.state.selectedAccount?.value === addr)
|
||||
.forEach(t => {
|
||||
const acc = t.state.selectedAccount
|
||||
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
|
||||
})
|
||||
}
|
||||
|
||||
@@ -1,53 +1,51 @@
|
||||
import { derive, sign } from "xrpl-accountlib";
|
||||
import toast from "react-hot-toast";
|
||||
import { derive, sign } from 'xrpl-accountlib'
|
||||
import toast from 'react-hot-toast'
|
||||
|
||||
import state, { IAccount } from "../index";
|
||||
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 state, { IAccount } from '../index'
|
||||
import calculateHookOn, { TTS } from '../../utils/hookOnCalculator'
|
||||
import { Link } from '../../components'
|
||||
import { ref } from 'valtio'
|
||||
import estimateFee from '../../utils/estimateFee'
|
||||
import { SetHookData } from '../../utils/setHook'
|
||||
|
||||
export const sha256 = async (string: string) => {
|
||||
const utf8 = new TextEncoder().encode(string);
|
||||
const hashBuffer = await crypto.subtle.digest("SHA-256", utf8);
|
||||
const hashArray = Array.from(new Uint8Array(hashBuffer));
|
||||
const hashHex = hashArray
|
||||
.map((bytes) => bytes.toString(16).padStart(2, "0"))
|
||||
.join("");
|
||||
return hashHex;
|
||||
};
|
||||
const utf8 = new TextEncoder().encode(string)
|
||||
const hashBuffer = await crypto.subtle.digest('SHA-256', utf8)
|
||||
const hashArray = Array.from(new Uint8Array(hashBuffer))
|
||||
const hashHex = hashArray.map(bytes => bytes.toString(16).padStart(2, '0')).join('')
|
||||
return hashHex
|
||||
}
|
||||
|
||||
function toHex(str: string) {
|
||||
var result = "";
|
||||
var result = ''
|
||||
for (var i = 0; i < str.length; i++) {
|
||||
result += str.charCodeAt(i).toString(16);
|
||||
result += str.charCodeAt(i).toString(16)
|
||||
}
|
||||
return result.toUpperCase();
|
||||
return result.toUpperCase()
|
||||
}
|
||||
|
||||
function arrayBufferToHex(arrayBuffer?: ArrayBuffer | null) {
|
||||
if (!arrayBuffer) {
|
||||
return "";
|
||||
return ''
|
||||
}
|
||||
if (
|
||||
typeof arrayBuffer !== "object" ||
|
||||
typeof arrayBuffer !== 'object' ||
|
||||
arrayBuffer === null ||
|
||||
typeof arrayBuffer.byteLength !== "number"
|
||||
typeof arrayBuffer.byteLength !== 'number'
|
||||
) {
|
||||
throw new TypeError("Expected input to be an ArrayBuffer");
|
||||
throw new TypeError('Expected input to be an ArrayBuffer')
|
||||
}
|
||||
|
||||
var view = new Uint8Array(arrayBuffer);
|
||||
var result = "";
|
||||
var value;
|
||||
var view = new Uint8Array(arrayBuffer)
|
||||
var result = ''
|
||||
var value
|
||||
|
||||
for (var i = 0; i < view.length; i++) {
|
||||
value = view[i].toString(16);
|
||||
result += value.length === 1 ? "0" + value : value;
|
||||
value = view[i].toString(16)
|
||||
result += value.length === 1 ? '0' + value : value
|
||||
}
|
||||
|
||||
return result;
|
||||
return result
|
||||
}
|
||||
|
||||
export const prepareDeployHookTx = async (
|
||||
@@ -56,30 +54,29 @@ export const prepareDeployHookTx = async (
|
||||
) => {
|
||||
const activeFile = state.files[state.active]?.compiledContent
|
||||
? state.files[state.active]
|
||||
: state.files.filter((file) => file.compiledContent)[0];
|
||||
: state.files.filter(file => file.compiledContent)[0]
|
||||
|
||||
if (!state.files || state.files.length === 0) {
|
||||
return;
|
||||
return
|
||||
}
|
||||
|
||||
if (!activeFile?.compiledContent) {
|
||||
return;
|
||||
return
|
||||
}
|
||||
if (!state.client) {
|
||||
return;
|
||||
return
|
||||
}
|
||||
const HookNamespace = (await sha256(data.HookNamespace)).toUpperCase();
|
||||
const hookOnValues: (keyof TTS)[] = data.Invoke.map((tt) => tt.value);
|
||||
const { HookParameters } = data;
|
||||
const HookNamespace = (await sha256(data.HookNamespace)).toUpperCase()
|
||||
const hookOnValues: (keyof TTS)[] = data.Invoke.map(tt => tt.value)
|
||||
const { HookParameters } = data
|
||||
const filteredHookParameters = HookParameters.filter(
|
||||
(hp) =>
|
||||
hp.HookParameter.HookParameterName && hp.HookParameter.HookParameterValue
|
||||
)?.map((aa) => ({
|
||||
hp => hp.HookParameter.HookParameterName && hp.HookParameter.HookParameterValue
|
||||
)?.map(aa => ({
|
||||
HookParameter: {
|
||||
HookParameterName: toHex(aa.HookParameter.HookParameterName || ""),
|
||||
HookParameterValue: aa.HookParameter.HookParameterValue || "",
|
||||
},
|
||||
}));
|
||||
HookParameterName: toHex(aa.HookParameter.HookParameterName || ''),
|
||||
HookParameterValue: aa.HookParameter.HookParameterValue || ''
|
||||
}
|
||||
}))
|
||||
// const filteredHookGrants = HookGrants.filter(hg => hg.HookGrant.Authorize || hg.HookGrant.HookHash).map(hg => {
|
||||
// return {
|
||||
// HookGrant: {
|
||||
@@ -89,82 +86,74 @@ export const prepareDeployHookTx = async (
|
||||
// }
|
||||
// }
|
||||
// });
|
||||
if (typeof window !== "undefined") {
|
||||
if (typeof window !== 'undefined') {
|
||||
const tx = {
|
||||
Account: account.address,
|
||||
TransactionType: "SetHook",
|
||||
TransactionType: 'SetHook',
|
||||
Sequence: account.sequence,
|
||||
Fee: data.Fee,
|
||||
Hooks: [
|
||||
{
|
||||
Hook: {
|
||||
CreateCode: arrayBufferToHex(
|
||||
activeFile?.compiledContent
|
||||
).toUpperCase(),
|
||||
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;
|
||||
HookParameters: filteredHookParameters
|
||||
})
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
return tx
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/* deployHook function 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") {
|
||||
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);
|
||||
: state.files.filter(file => file.compiledContent)[0]
|
||||
state.deployValues[activeFile.name] = data
|
||||
const tx = await prepareDeployHookTx(account, data)
|
||||
if (!tx) {
|
||||
return;
|
||||
return
|
||||
}
|
||||
if (!state.client) {
|
||||
return;
|
||||
return
|
||||
}
|
||||
const keypair = derive.familySeed(account.secret);
|
||||
const keypair = derive.familySeed(account.secret)
|
||||
|
||||
const { signedTransaction } = sign(tx, keypair);
|
||||
const currentAccount = state.accounts.find(
|
||||
(acc) => acc.address === account.address
|
||||
);
|
||||
const { signedTransaction } = sign(tx, keypair)
|
||||
const currentAccount = state.accounts.find(acc => acc.address === account.address)
|
||||
if (currentAccount) {
|
||||
currentAccount.isLoading = true;
|
||||
currentAccount.isLoading = true
|
||||
}
|
||||
let submitRes;
|
||||
let submitRes
|
||||
|
||||
try {
|
||||
submitRes = await state.client?.send({
|
||||
command: "submit",
|
||||
tx_blob: signedTransaction,
|
||||
});
|
||||
command: 'submit',
|
||||
tx_blob: signedTransaction
|
||||
})
|
||||
|
||||
if (submitRes.engine_result === "tesSUCCESS") {
|
||||
if (submitRes.engine_result === 'tesSUCCESS') {
|
||||
state.deployLogs.push({
|
||||
type: "success",
|
||||
message: "Hook deployed successfully ✅",
|
||||
});
|
||||
type: 'success',
|
||||
message: 'Hook deployed successfully ✅'
|
||||
})
|
||||
state.deployLogs.push({
|
||||
type: "success",
|
||||
type: 'success',
|
||||
message: ref(
|
||||
<>
|
||||
[{submitRes.engine_result}] {submitRes.engine_result_message}{" "}
|
||||
Transaction hash:{" "}
|
||||
[{submitRes.engine_result}] {submitRes.engine_result_message} Transaction hash:{' '}
|
||||
<Link
|
||||
as="a"
|
||||
href={`https://${process.env.NEXT_PUBLIC_EXPLORER_URL}/${submitRes.tx_json?.hash}`}
|
||||
@@ -174,113 +163,110 @@ export const deployHook = async (
|
||||
{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",
|
||||
type: 'error',
|
||||
message: `[${submitRes.engine_result || submitRes.error}] ${
|
||||
submitRes.engine_result_message || submitRes.error_exception
|
||||
}`,
|
||||
});
|
||||
}`
|
||||
})
|
||||
}
|
||||
} catch (err) {
|
||||
console.log(err);
|
||||
console.log(err)
|
||||
state.deployLogs.push({
|
||||
type: "error",
|
||||
message: "Error occurred while deploying",
|
||||
});
|
||||
type: 'error',
|
||||
message: 'Error occurred while deploying'
|
||||
})
|
||||
}
|
||||
if (currentAccount) {
|
||||
currentAccount.isLoading = false;
|
||||
currentAccount.isLoading = false
|
||||
}
|
||||
return submitRes;
|
||||
return submitRes
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
export const deleteHook = async (account: IAccount & { name?: string }) => {
|
||||
if (!state.client) {
|
||||
return;
|
||||
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) {
|
||||
return;
|
||||
return
|
||||
}
|
||||
if (typeof window !== "undefined") {
|
||||
if (typeof window !== 'undefined') {
|
||||
const tx = {
|
||||
Account: account.address,
|
||||
TransactionType: "SetHook",
|
||||
TransactionType: 'SetHook',
|
||||
Sequence: account.sequence,
|
||||
Fee: "100000",
|
||||
Fee: '100000',
|
||||
Hooks: [
|
||||
{
|
||||
Hook: {
|
||||
CreateCode: "",
|
||||
Flags: 1,
|
||||
},
|
||||
},
|
||||
],
|
||||
};
|
||||
CreateCode: '',
|
||||
Flags: 1
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
const keypair = derive.familySeed(account.secret);
|
||||
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";
|
||||
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);
|
||||
console.log(err)
|
||||
}
|
||||
const { signedTransaction } = sign(tx, keypair);
|
||||
const { signedTransaction } = sign(tx, keypair)
|
||||
|
||||
if (currentAccount) {
|
||||
currentAccount.isLoading = true;
|
||||
currentAccount.isLoading = true
|
||||
}
|
||||
let submitRes;
|
||||
const toastId = toast.loading("Deleting hook...");
|
||||
let submitRes
|
||||
const toastId = toast.loading('Deleting hook...')
|
||||
try {
|
||||
submitRes = await state.client.send({
|
||||
command: "submit",
|
||||
tx_blob: signedTransaction,
|
||||
});
|
||||
command: 'submit',
|
||||
tx_blob: signedTransaction
|
||||
})
|
||||
|
||||
if (submitRes.engine_result === "tesSUCCESS") {
|
||||
toast.success("Hook deleted successfully ✅", { id: toastId });
|
||||
if (submitRes.engine_result === 'tesSUCCESS') {
|
||||
toast.success('Hook deleted successfully ✅', { id: toastId })
|
||||
state.deployLogs.push({
|
||||
type: "success",
|
||||
message: "Hook deleted successfully ✅",
|
||||
});
|
||||
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 = [];
|
||||
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 }
|
||||
);
|
||||
toast.error(`${submitRes.engine_result_message || submitRes.error_exception}`, {
|
||||
id: toastId
|
||||
})
|
||||
state.deployLogs.push({
|
||||
type: "error",
|
||||
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 });
|
||||
console.log(err)
|
||||
toast.error('Error occurred while deleting hook', { id: toastId })
|
||||
state.deployLogs.push({
|
||||
type: "error",
|
||||
message: "Error occurred while deleting hook",
|
||||
});
|
||||
type: 'error',
|
||||
message: 'Error occurred while deleting hook'
|
||||
})
|
||||
}
|
||||
if (currentAccount) {
|
||||
currentAccount.isLoading = false;
|
||||
currentAccount.isLoading = false
|
||||
}
|
||||
return submitRes;
|
||||
return submitRes
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@@ -1,20 +1,22 @@
|
||||
import { createZip } from '../../utils/zip';
|
||||
import { guessZipFileName } from '../../utils/helpers';
|
||||
import { createZip } from '../../utils/zip'
|
||||
import { guessZipFileName } from '../../utils/helpers'
|
||||
import state from '..'
|
||||
import toast from 'react-hot-toast';
|
||||
import toast from 'react-hot-toast'
|
||||
|
||||
export const downloadAsZip = async () => {
|
||||
try {
|
||||
state.zipLoading = true
|
||||
// TODO do something about file/gist loading state
|
||||
const files = state.files.map(({ name, content }) => ({ name, content }));
|
||||
const wasmFiles = state.files.filter(i => i.compiledContent).map(({ name, compiledContent }) => ({ name: `${name}.wasm`, content: compiledContent }));
|
||||
const zipped = await createZip([...files, ...wasmFiles]);
|
||||
const zipFileName = guessZipFileName(files);
|
||||
zipped.saveFile(zipFileName);
|
||||
} catch (error) {
|
||||
toast.error('Error occurred while creating zip file, try again later')
|
||||
} finally {
|
||||
state.zipLoading = false
|
||||
}
|
||||
};
|
||||
try {
|
||||
state.zipLoading = true
|
||||
// TODO do something about file/gist loading state
|
||||
const files = state.files.map(({ name, content }) => ({ name, content }))
|
||||
const wasmFiles = state.files
|
||||
.filter(i => i.compiledContent)
|
||||
.map(({ name, compiledContent }) => ({ name: `${name}.wasm`, content: compiledContent }))
|
||||
const zipped = await createZip([...files, ...wasmFiles])
|
||||
const zipFileName = guessZipFileName(files)
|
||||
zipped.saveFile(zipFileName)
|
||||
} catch (error) {
|
||||
toast.error('Error occurred while creating zip file, try again later')
|
||||
} finally {
|
||||
state.zipLoading = false
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,50 +1,57 @@
|
||||
import { Octokit } from "@octokit/core";
|
||||
import Router from "next/router";
|
||||
import state from '../index';
|
||||
import { templateFileIds } from '../constants';
|
||||
import { Octokit } from '@octokit/core'
|
||||
import Router from 'next/router'
|
||||
import state from '../index'
|
||||
import { templateFileIds } from '../constants'
|
||||
|
||||
const octokit = new Octokit();
|
||||
const octokit = new Octokit()
|
||||
|
||||
/* Fetches Gist files from Githug Gists based on
|
||||
* gistId and stores the content in global state
|
||||
*/
|
||||
export const fetchFiles = (gistId: string) => {
|
||||
state.loading = true;
|
||||
state.loading = true
|
||||
if (gistId && !state.files.length) {
|
||||
state.logs.push({
|
||||
type: "log",
|
||||
message: `Fetching Gist with id: ${gistId}`,
|
||||
});
|
||||
type: 'log',
|
||||
message: `Fetching Gist with id: ${gistId}`
|
||||
})
|
||||
|
||||
octokit
|
||||
.request("GET /gists/{gist_id}", { gist_id: gistId })
|
||||
.request('GET /gists/{gist_id}', { gist_id: gistId })
|
||||
.then(async res => {
|
||||
if (!Object.values(templateFileIds).map(v => v.id).includes(gistId)) {
|
||||
if (
|
||||
!Object.values(templateFileIds)
|
||||
.map(v => v.id)
|
||||
.includes(gistId)
|
||||
) {
|
||||
return res
|
||||
}
|
||||
// in case of templates, fetch header file(s) and append to res
|
||||
try {
|
||||
const resHeader = await fetch(`${process.env.NEXT_PUBLIC_COMPILE_API_BASE_URL}/api/header-files`);
|
||||
const resHeader = await fetch(
|
||||
`${process.env.NEXT_PUBLIC_COMPILE_API_BASE_URL}/api/header-files`
|
||||
)
|
||||
if (resHeader.ok) {
|
||||
const resHeaderJson = await resHeader.json()
|
||||
const headerFiles: Record<string, { filename: string; content: string; language: string }> = {};
|
||||
const headerFiles: Record<
|
||||
string,
|
||||
{ filename: string; content: string; language: string }
|
||||
> = {}
|
||||
Object.entries(resHeaderJson).forEach(([key, value]) => {
|
||||
const fname = `${key}.h`;
|
||||
const fname = `${key}.h`
|
||||
headerFiles[fname] = { filename: fname, content: value as string, language: 'C' }
|
||||
})
|
||||
const files = {
|
||||
...res.data.files,
|
||||
...headerFiles
|
||||
};
|
||||
res.data.files = files;
|
||||
}
|
||||
res.data.files = files
|
||||
}
|
||||
} catch (err) {
|
||||
console.log(err)
|
||||
}
|
||||
|
||||
|
||||
|
||||
return res;
|
||||
return res
|
||||
// If you want to load templates from GIST instad, uncomment the code below and comment the code above.
|
||||
// return octokit.request("GET /gists/{gist_id}", { gist_id: templateFileIds.headers }).then(({ data: { files: headerFiles } }) => {
|
||||
// const files = { ...res.data.files, ...headerFiles }
|
||||
@@ -53,65 +60,65 @@ export const fetchFiles = (gistId: string) => {
|
||||
// return res
|
||||
// })
|
||||
})
|
||||
.then((res) => {
|
||||
.then(res => {
|
||||
if (res.data.files && Object.keys(res.data.files).length > 0) {
|
||||
const files = Object.keys(res.data.files).map((filename) => ({
|
||||
name: res.data.files?.[filename]?.filename || "untitled.c",
|
||||
language: res.data.files?.[filename]?.language?.toLowerCase() || "",
|
||||
content: res.data.files?.[filename]?.content || "",
|
||||
}));
|
||||
const files = Object.keys(res.data.files).map(filename => ({
|
||||
name: res.data.files?.[filename]?.filename || 'untitled.c',
|
||||
language: res.data.files?.[filename]?.language?.toLowerCase() || '',
|
||||
content: res.data.files?.[filename]?.content || ''
|
||||
}))
|
||||
// Sort files so that the source files are first
|
||||
// In case of other files leave the order as it its
|
||||
files.sort((a, b) => {
|
||||
const aBasename = a.name.split('.')?.[0];
|
||||
const aCext = a.name?.toLowerCase().endsWith('.c');
|
||||
const bBasename = b.name.split('.')?.[0];
|
||||
const bCext = b.name?.toLowerCase().endsWith('.c');
|
||||
const aBasename = a.name.split('.')?.[0]
|
||||
const aCext = a.name?.toLowerCase().endsWith('.c')
|
||||
const bBasename = b.name.split('.')?.[0]
|
||||
const bCext = b.name?.toLowerCase().endsWith('.c')
|
||||
// If a has c extension and b doesn't move a up
|
||||
if (aCext && !bCext) {
|
||||
return -1;
|
||||
return -1
|
||||
}
|
||||
if (!aCext && bCext) {
|
||||
return 1
|
||||
}
|
||||
// Otherwise fallback to default sorting based on basename
|
||||
if (aBasename > bBasename) {
|
||||
return 1;
|
||||
return 1
|
||||
}
|
||||
if (bBasename > aBasename) {
|
||||
return -1;
|
||||
return -1
|
||||
}
|
||||
return 0;
|
||||
return 0
|
||||
})
|
||||
state.loading = false;
|
||||
state.loading = false
|
||||
if (files.length > 0) {
|
||||
state.logs.push({
|
||||
type: "success",
|
||||
message: "Fetched successfully ✅",
|
||||
});
|
||||
state.files = files;
|
||||
state.gistId = gistId;
|
||||
state.gistName = Object.keys(res.data.files)?.[0] || "untitled";
|
||||
state.gistOwner = res.data.owner?.login;
|
||||
return;
|
||||
type: 'success',
|
||||
message: 'Fetched successfully ✅'
|
||||
})
|
||||
state.files = files
|
||||
state.gistId = gistId
|
||||
state.gistName = Object.keys(res.data.files)?.[0] || 'untitled'
|
||||
state.gistOwner = res.data.owner?.login
|
||||
return
|
||||
} else {
|
||||
// Open main modal if now files
|
||||
state.mainModalOpen = true;
|
||||
state.mainModalOpen = true
|
||||
}
|
||||
return Router.push({ pathname: "/develop" });
|
||||
return Router.push({ pathname: '/develop' })
|
||||
}
|
||||
state.loading = false;
|
||||
state.loading = false
|
||||
})
|
||||
.catch((err) => {
|
||||
.catch(err => {
|
||||
// console.error(err)
|
||||
state.loading = false;
|
||||
state.loading = false
|
||||
state.logs.push({
|
||||
type: "error",
|
||||
message: `Couldn't find Gist with id: ${gistId}`,
|
||||
});
|
||||
return;
|
||||
});
|
||||
return;
|
||||
type: 'error',
|
||||
message: `Couldn't find Gist with id: ${gistId}`
|
||||
})
|
||||
return
|
||||
})
|
||||
return
|
||||
}
|
||||
state.loading = false;
|
||||
};
|
||||
state.loading = false
|
||||
}
|
||||
|
||||
@@ -1,40 +1,40 @@
|
||||
import toast from "react-hot-toast";
|
||||
import { derive, XRPL_Account } from "xrpl-accountlib";
|
||||
import toast from 'react-hot-toast'
|
||||
import { derive, XRPL_Account } from 'xrpl-accountlib'
|
||||
|
||||
import state from '../index';
|
||||
import { names } from './addFaucetAccount';
|
||||
import state from '../index'
|
||||
import { names } from './addFaucetAccount'
|
||||
|
||||
// Adds test account to global state with secret key
|
||||
export const importAccount = (secret: string, name?: string) => {
|
||||
if (!secret) {
|
||||
return toast.error("You need to add secret!");
|
||||
return toast.error('You need to add secret!')
|
||||
}
|
||||
if (state.accounts.find((acc) => acc.secret === secret)) {
|
||||
return toast.error("Account already added!");
|
||||
if (state.accounts.find(acc => acc.secret === secret)) {
|
||||
return toast.error('Account already added!')
|
||||
}
|
||||
let account: XRPL_Account | null = null;
|
||||
let account: XRPL_Account | null = null
|
||||
try {
|
||||
account = derive.familySeed(secret);
|
||||
account = derive.familySeed(secret)
|
||||
} catch (err: any) {
|
||||
if (err?.message) {
|
||||
toast.error(err.message)
|
||||
} else {
|
||||
toast.error('Error occurred while importing account')
|
||||
}
|
||||
return;
|
||||
return
|
||||
}
|
||||
if (!account || !account.secret.familySeed) {
|
||||
return toast.error(`Couldn't create account!`);
|
||||
return toast.error(`Couldn't create account!`)
|
||||
}
|
||||
state.accounts.push({
|
||||
name: name || names[state.accounts.length],
|
||||
address: account.address || "",
|
||||
secret: account.secret.familySeed || "",
|
||||
xrp: "0",
|
||||
address: account.address || '',
|
||||
secret: account.secret.familySeed || '',
|
||||
xrp: '0',
|
||||
sequence: 1,
|
||||
hooks: [],
|
||||
isLoading: false,
|
||||
version: '2'
|
||||
});
|
||||
return toast.success("Account imported successfully!");
|
||||
};
|
||||
})
|
||||
return toast.success('Account imported successfully!')
|
||||
}
|
||||
|
||||
@@ -1,14 +1,14 @@
|
||||
import { addFaucetAccount } from "./addFaucetAccount";
|
||||
import { compileCode } from "./compileCode";
|
||||
import { createNewFile } from "./createNewFile";
|
||||
import { deployHook } from "./deployHook";
|
||||
import { fetchFiles } from "./fetchFiles";
|
||||
import { importAccount } from "./importAccount";
|
||||
import { saveFile } from "./saveFile";
|
||||
import { syncToGist } from "./syncToGist";
|
||||
import { updateEditorSettings } from "./updateEditorSettings";
|
||||
import { downloadAsZip } from "./downloadAsZip";
|
||||
import { sendTransaction } from "./sendTransaction";
|
||||
import { addFaucetAccount } from './addFaucetAccount'
|
||||
import { compileCode } from './compileCode'
|
||||
import { createNewFile } from './createNewFile'
|
||||
import { deployHook } from './deployHook'
|
||||
import { fetchFiles } from './fetchFiles'
|
||||
import { importAccount } from './importAccount'
|
||||
import { saveFile } from './saveFile'
|
||||
import { syncToGist } from './syncToGist'
|
||||
import { updateEditorSettings } from './updateEditorSettings'
|
||||
import { downloadAsZip } from './downloadAsZip'
|
||||
import { sendTransaction } from './sendTransaction'
|
||||
|
||||
export {
|
||||
addFaucetAccount,
|
||||
@@ -22,4 +22,4 @@ export {
|
||||
updateEditorSettings,
|
||||
downloadAsZip,
|
||||
sendTransaction
|
||||
};
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { snapshot } from "valtio"
|
||||
import state from ".."
|
||||
import { snapshot } from 'valtio'
|
||||
import state from '..'
|
||||
|
||||
export type SplitSize = number[]
|
||||
|
||||
@@ -12,4 +12,3 @@ export const getSplit = (splitId: string): SplitSize | null => {
|
||||
const split = splits[splitId]
|
||||
return split ? split : null
|
||||
}
|
||||
|
||||
|
||||
@@ -1,28 +1,28 @@
|
||||
import toast from "react-hot-toast";
|
||||
import state from '../index';
|
||||
import toast from 'react-hot-toast'
|
||||
import state from '../index'
|
||||
|
||||
// Saves the current editor content to global state
|
||||
export const saveFile = (showToast: boolean = true, activeId?: number) => {
|
||||
const editorModels = state.editorCtx?.getModels();
|
||||
const sought = '/' + state.files[state.active].name;
|
||||
const currentModel = editorModels?.find((editorModel) => {
|
||||
return editorModel.uri.path.endsWith(sought);
|
||||
});
|
||||
const editorModels = state.editorCtx?.getModels()
|
||||
const sought = '/' + state.files[state.active].name
|
||||
const currentModel = editorModels?.find(editorModel => {
|
||||
return editorModel.uri.path.endsWith(sought)
|
||||
})
|
||||
const file = state.files[activeId || state.active]
|
||||
if (state.files.length > 0) {
|
||||
file.content = currentModel?.getValue() || "";
|
||||
file.content = currentModel?.getValue() || ''
|
||||
}
|
||||
if (showToast) {
|
||||
toast.success("Saved successfully", { position: "bottom-center" });
|
||||
toast.success('Saved successfully', { position: 'bottom-center' })
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
export const saveAllFiles = () => {
|
||||
const editorModels = state.editorCtx?.getModels();
|
||||
const editorModels = state.editorCtx?.getModels()
|
||||
state.files.forEach(file => {
|
||||
const currentModel = editorModels?.find(model => model.uri.path.endsWith('/' + file.name))
|
||||
if (currentModel) {
|
||||
file.content = currentModel?.getValue() || '';
|
||||
file.content = currentModel?.getValue() || ''
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
@@ -1,57 +1,66 @@
|
||||
import { derive, sign } from "xrpl-accountlib";
|
||||
import { derive, sign } from 'xrpl-accountlib'
|
||||
|
||||
import state from '..'
|
||||
import type { IAccount } from "..";
|
||||
import type { IAccount } from '..'
|
||||
|
||||
interface TransactionOptions {
|
||||
TransactionType: string,
|
||||
Account?: string,
|
||||
Fee?: string,
|
||||
Destination?: string
|
||||
[index: string]: any
|
||||
TransactionType: string
|
||||
Account?: string
|
||||
Fee?: string
|
||||
Destination?: string
|
||||
[index: string]: any
|
||||
}
|
||||
interface OtherOptions {
|
||||
logPrefix?: string
|
||||
logPrefix?: string
|
||||
}
|
||||
|
||||
export const sendTransaction = async (account: IAccount, txOptions: TransactionOptions, options?: OtherOptions) => {
|
||||
if (!state.client) throw Error('XRPL client not initalized')
|
||||
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`,
|
||||
});
|
||||
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`
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,23 +1,27 @@
|
||||
import { ref } from 'valtio';
|
||||
import { AlertState, alertState } from "../../components/AlertDialog";
|
||||
import { ref } from 'valtio'
|
||||
import { AlertState, alertState } from '../../components/AlertDialog'
|
||||
|
||||
export const showAlert = (title: string, opts: Omit<Partial<AlertState>, 'title' | 'isOpen'> = {}) => {
|
||||
const { body: _body, confirmPrefix: _confirmPrefix, ...rest } = opts
|
||||
const body = (_body && typeof _body === 'object') ? ref(_body) : _body
|
||||
const confirmPrefix = (_confirmPrefix && typeof _confirmPrefix === 'object') ? ref(_confirmPrefix) : _confirmPrefix
|
||||
export const showAlert = (
|
||||
title: string,
|
||||
opts: Omit<Partial<AlertState>, 'title' | 'isOpen'> = {}
|
||||
) => {
|
||||
const { body: _body, confirmPrefix: _confirmPrefix, ...rest } = opts
|
||||
const body = _body && typeof _body === 'object' ? ref(_body) : _body
|
||||
const confirmPrefix =
|
||||
_confirmPrefix && typeof _confirmPrefix === 'object' ? ref(_confirmPrefix) : _confirmPrefix
|
||||
|
||||
const nwState: AlertState = {
|
||||
isOpen: true,
|
||||
title,
|
||||
body,
|
||||
confirmPrefix,
|
||||
cancelText: undefined,
|
||||
confirmText: undefined,
|
||||
onCancel: undefined,
|
||||
onConfirm: undefined,
|
||||
...rest,
|
||||
}
|
||||
Object.entries(nwState).forEach(([key, value]) => {
|
||||
(alertState as any)[key] = value
|
||||
})
|
||||
}
|
||||
const nwState: AlertState = {
|
||||
isOpen: true,
|
||||
title,
|
||||
body,
|
||||
confirmPrefix,
|
||||
cancelText: undefined,
|
||||
confirmText: undefined,
|
||||
onCancel: undefined,
|
||||
onConfirm: undefined,
|
||||
...rest
|
||||
}
|
||||
Object.entries(nwState).forEach(([key, value]) => {
|
||||
;(alertState as any)[key] = value
|
||||
})
|
||||
}
|
||||
|
||||
@@ -1,104 +1,97 @@
|
||||
import type { Session } from "next-auth";
|
||||
import toast from "react-hot-toast";
|
||||
import { Octokit } from "@octokit/core";
|
||||
import Router from "next/router";
|
||||
import type { Session } from 'next-auth'
|
||||
import toast from 'react-hot-toast'
|
||||
import { Octokit } from '@octokit/core'
|
||||
import Router from 'next/router'
|
||||
|
||||
import state from '../index';
|
||||
import { saveAllFiles } from "./saveFile";
|
||||
import state from '../index'
|
||||
import { saveAllFiles } from './saveFile'
|
||||
|
||||
const octokit = new Octokit();
|
||||
const octokit = new Octokit()
|
||||
|
||||
// Syncs the current files from the state to GitHub Gists.
|
||||
export const syncToGist = async (
|
||||
session?: Session | null,
|
||||
createNewGist?: boolean
|
||||
) => {
|
||||
saveAllFiles();
|
||||
let files: Record<string, { filename: string; content: string }> = {};
|
||||
state.gistLoading = true;
|
||||
export const syncToGist = async (session?: Session | null, createNewGist?: boolean) => {
|
||||
saveAllFiles()
|
||||
let files: Record<string, { filename: string; content: string }> = {}
|
||||
state.gistLoading = true
|
||||
|
||||
if (!session || !session.user) {
|
||||
state.gistLoading = false;
|
||||
return toast.error("You need to be logged in!");
|
||||
state.gistLoading = false
|
||||
return toast.error('You need to be logged in!')
|
||||
}
|
||||
const toastId = toast.loading("Pushing to Gist");
|
||||
const toastId = toast.loading('Pushing to Gist')
|
||||
if (!state.files || !state.files.length) {
|
||||
state.gistLoading = false;
|
||||
state.gistLoading = false
|
||||
return toast.error(`You need to create some files we can push to gist`, {
|
||||
id: toastId,
|
||||
});
|
||||
id: toastId
|
||||
})
|
||||
}
|
||||
if (
|
||||
state.gistId &&
|
||||
session?.user.username === state.gistOwner &&
|
||||
!createNewGist
|
||||
) {
|
||||
if (state.gistId && session?.user.username === state.gistOwner && !createNewGist) {
|
||||
// You can only remove files from Gist by updating file with empty contents
|
||||
// So we need to fetch existing files and compare those to local state
|
||||
// and then send empty content if we don't have matching files anymore
|
||||
// on local state
|
||||
const currentFilesRes = await octokit.request("GET /gists/{gist_id}", {
|
||||
gist_id: state.gistId,
|
||||
});
|
||||
const currentFilesRes = await octokit.request('GET /gists/{gist_id}', {
|
||||
gist_id: state.gistId
|
||||
})
|
||||
if (currentFilesRes.data.files) {
|
||||
Object.keys(currentFilesRes?.data?.files).forEach((filename) => {
|
||||
files[`${filename}`] = { filename, content: "" };
|
||||
});
|
||||
Object.keys(currentFilesRes?.data?.files).forEach(filename => {
|
||||
files[`${filename}`] = { filename, content: '' }
|
||||
})
|
||||
}
|
||||
state.files.forEach((file) => {
|
||||
files[`${file.name}`] = { filename: file.name, content: file.content };
|
||||
});
|
||||
state.files.forEach(file => {
|
||||
files[`${file.name}`] = { filename: file.name, content: file.content }
|
||||
})
|
||||
// Update existing Gist
|
||||
octokit
|
||||
.request("PATCH /gists/{gist_id}", {
|
||||
.request('PATCH /gists/{gist_id}', {
|
||||
gist_id: state.gistId,
|
||||
files,
|
||||
headers: {
|
||||
authorization: `token ${session?.accessToken || ""}`,
|
||||
},
|
||||
authorization: `token ${session?.accessToken || ''}`
|
||||
}
|
||||
})
|
||||
.then((res) => {
|
||||
state.gistLoading = false;
|
||||
return toast.success("Updated to gist successfully!", { id: toastId });
|
||||
.then(res => {
|
||||
state.gistLoading = false
|
||||
return toast.success('Updated to gist successfully!', { id: toastId })
|
||||
})
|
||||
.catch((err) => {
|
||||
console.log(err);
|
||||
state.gistLoading = false;
|
||||
.catch(err => {
|
||||
console.log(err)
|
||||
state.gistLoading = false
|
||||
return toast.error(`Could not update Gist, try again later!`, {
|
||||
id: toastId,
|
||||
});
|
||||
});
|
||||
id: toastId
|
||||
})
|
||||
})
|
||||
} else {
|
||||
// Not Gist of the current user or it isn't Gist yet
|
||||
state.files.forEach((file) => {
|
||||
files[`${file.name}`] = { filename: file.name, content: file.content };
|
||||
});
|
||||
state.files.forEach(file => {
|
||||
files[`${file.name}`] = { filename: file.name, content: file.content }
|
||||
})
|
||||
octokit
|
||||
.request("POST /gists", {
|
||||
.request('POST /gists', {
|
||||
files,
|
||||
public: true,
|
||||
headers: {
|
||||
authorization: `token ${session?.accessToken || ""}`,
|
||||
},
|
||||
authorization: `token ${session?.accessToken || ''}`
|
||||
}
|
||||
})
|
||||
.then((res) => {
|
||||
state.gistLoading = false;
|
||||
state.gistOwner = res.data.owner?.login;
|
||||
state.gistId = res.data.id;
|
||||
.then(res => {
|
||||
state.gistLoading = false
|
||||
state.gistOwner = res.data.owner?.login
|
||||
state.gistId = res.data.id
|
||||
state.gistName = Array.isArray(res.data.files)
|
||||
? Object.keys(res.data?.files)?.[0]
|
||||
: "Untitled";
|
||||
Router.push({ pathname: `/develop/${res.data.id}` });
|
||||
return toast.success("Created new gist successfully!", { id: toastId });
|
||||
: 'Untitled'
|
||||
Router.push({ pathname: `/develop/${res.data.id}` })
|
||||
return toast.success('Created new gist successfully!', { id: toastId })
|
||||
})
|
||||
.catch((err) => {
|
||||
console.log(err);
|
||||
state.gistLoading = false;
|
||||
.catch(err => {
|
||||
console.log(err)
|
||||
state.gistLoading = false
|
||||
return toast.error(`Could not create Gist, try again later!`, {
|
||||
id: toastId,
|
||||
});
|
||||
});
|
||||
id: toastId
|
||||
})
|
||||
})
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
export default syncToGist;
|
||||
export default syncToGist
|
||||
|
||||
@@ -1,14 +1,12 @@
|
||||
import state, { IState } from '../index';
|
||||
import state, { IState } from '../index'
|
||||
|
||||
// Updates editor settings and stores them
|
||||
// in global state
|
||||
export const updateEditorSettings = (
|
||||
editorSettings: IState["editorSettings"]
|
||||
) => {
|
||||
state.editorCtx?.getModels().forEach((model) => {
|
||||
export const updateEditorSettings = (editorSettings: IState['editorSettings']) => {
|
||||
state.editorCtx?.getModels().forEach(model => {
|
||||
model.updateOptions({
|
||||
...editorSettings,
|
||||
});
|
||||
});
|
||||
return (state.editorSettings = editorSettings);
|
||||
};
|
||||
...editorSettings
|
||||
})
|
||||
})
|
||||
return (state.editorSettings = editorSettings)
|
||||
}
|
||||
|
||||
@@ -1 +1 @@
|
||||
export * from './templates'
|
||||
export * from './templates'
|
||||
|
||||
@@ -1,41 +1,41 @@
|
||||
import Carbon from "../../components/icons/Carbon";
|
||||
import Firewall from "../../components/icons/Firewall";
|
||||
import Notary from "../../components/icons/Notary";
|
||||
import Peggy from "../../components/icons/Peggy";
|
||||
import Starter from "../../components/icons/Starter";
|
||||
import Carbon from '../../components/icons/Carbon'
|
||||
import Firewall from '../../components/icons/Firewall'
|
||||
import Notary from '../../components/icons/Notary'
|
||||
import Peggy from '../../components/icons/Peggy'
|
||||
import Starter from '../../components/icons/Starter'
|
||||
|
||||
export const templateFileIds = {
|
||||
'starter': {
|
||||
id: '9106f1fe60482d90475bfe8f1315affe',
|
||||
name: 'Starter',
|
||||
description: 'Just a basic starter with essential imports, just accepts any transaction coming through',
|
||||
icon: Starter
|
||||
|
||||
},
|
||||
'firewall': {
|
||||
id: '1cc30f39c8a0b9c55b88c312669ca45e', // Forked
|
||||
name: 'Firewall',
|
||||
description: 'This Hook essentially checks a blacklist of accounts',
|
||||
icon: Firewall
|
||||
},
|
||||
'notary': {
|
||||
id: '87b6f5a8c2f5038fb0f20b8b510efa10', // Forked
|
||||
name: 'Notary',
|
||||
description: 'Collecting signatures for multi-sign transactions',
|
||||
icon: Notary
|
||||
},
|
||||
'carbon': {
|
||||
id: '5941c19dce3e147948f564e224553c02',
|
||||
name: 'Carbon',
|
||||
description: 'Send a percentage of sum to an address',
|
||||
icon: Carbon
|
||||
},
|
||||
'peggy': {
|
||||
id: '049784a83fa068faf7912f663f7b6471', // Forked
|
||||
name: 'Peggy',
|
||||
description: 'An oracle based stable coin hook',
|
||||
icon: Peggy
|
||||
},
|
||||
starter: {
|
||||
id: '9106f1fe60482d90475bfe8f1315affe',
|
||||
name: 'Starter',
|
||||
description:
|
||||
'Just a basic starter with essential imports, just accepts any transaction coming through',
|
||||
icon: Starter
|
||||
},
|
||||
firewall: {
|
||||
id: '1cc30f39c8a0b9c55b88c312669ca45e', // Forked
|
||||
name: 'Firewall',
|
||||
description: 'This Hook essentially checks a blacklist of accounts',
|
||||
icon: Firewall
|
||||
},
|
||||
notary: {
|
||||
id: '87b6f5a8c2f5038fb0f20b8b510efa10', // Forked
|
||||
name: 'Notary',
|
||||
description: 'Collecting signatures for multi-sign transactions',
|
||||
icon: Notary
|
||||
},
|
||||
carbon: {
|
||||
id: '5941c19dce3e147948f564e224553c02',
|
||||
name: 'Carbon',
|
||||
description: 'Send a percentage of sum to an address',
|
||||
icon: Carbon
|
||||
},
|
||||
peggy: {
|
||||
id: '049784a83fa068faf7912f663f7b6471', // Forked
|
||||
name: 'Peggy',
|
||||
description: 'An oracle based stable coin hook',
|
||||
icon: Peggy
|
||||
}
|
||||
}
|
||||
|
||||
export const apiHeaderFiles = ['hookapi.h', 'sfcodes.h', 'macro.h', 'extern.h', 'error.h'];
|
||||
export const apiHeaderFiles = ['hookapi.h', 'sfcodes.h', 'macro.h', 'extern.h', 'error.h']
|
||||
|
||||
179
state/index.ts
179
state/index.ts
@@ -1,92 +1,92 @@
|
||||
import type monaco from "monaco-editor";
|
||||
import { proxy, ref, subscribe } from "valtio";
|
||||
import { devtools, subscribeKey } from 'valtio/utils';
|
||||
import { XrplClient } from "xrpl-client";
|
||||
import { SplitSize } from "./actions/persistSplits";
|
||||
import type monaco from 'monaco-editor'
|
||||
import { proxy, ref, subscribe } from 'valtio'
|
||||
import { devtools, subscribeKey } from 'valtio/utils'
|
||||
import { XrplClient } from 'xrpl-client'
|
||||
import { SplitSize } from './actions/persistSplits'
|
||||
|
||||
declare module "valtio" {
|
||||
function useSnapshot<T extends object>(p: T): T;
|
||||
function snapshot<T extends object>(p: T): T;
|
||||
declare module 'valtio' {
|
||||
function useSnapshot<T extends object>(p: T): T
|
||||
function snapshot<T extends object>(p: T): T
|
||||
}
|
||||
|
||||
export interface IFile {
|
||||
name: string;
|
||||
language: string;
|
||||
content: string;
|
||||
name: string
|
||||
language: string
|
||||
content: string
|
||||
compiledValueSnapshot?: string
|
||||
compiledContent?: ArrayBuffer | null;
|
||||
compiledWatContent?: string | null;
|
||||
compiledContent?: ArrayBuffer | null
|
||||
compiledWatContent?: string | null
|
||||
lastCompiled?: Date
|
||||
containsErrors?: boolean
|
||||
}
|
||||
|
||||
export interface FaucetAccountRes {
|
||||
address: string;
|
||||
secret: string;
|
||||
xrp: number;
|
||||
hash: string;
|
||||
code: string;
|
||||
address: string
|
||||
secret: string
|
||||
xrp: number
|
||||
hash: string
|
||||
code: string
|
||||
}
|
||||
|
||||
export interface IAccount {
|
||||
name: string;
|
||||
address: string;
|
||||
secret: string;
|
||||
xrp: string;
|
||||
sequence: number;
|
||||
hooks: string[];
|
||||
isLoading: boolean;
|
||||
version?: string;
|
||||
name: string
|
||||
address: string
|
||||
secret: string
|
||||
xrp: string
|
||||
sequence: number
|
||||
hooks: string[]
|
||||
isLoading: boolean
|
||||
version?: string
|
||||
error?: {
|
||||
message: string;
|
||||
code: string;
|
||||
} | null;
|
||||
message: string
|
||||
code: string
|
||||
} | null
|
||||
}
|
||||
|
||||
export interface ILog {
|
||||
type: "error" | "warning" | "log" | "success";
|
||||
message: string | JSX.Element;
|
||||
key?: string;
|
||||
jsonData?: any,
|
||||
timestring?: string;
|
||||
link?: string;
|
||||
linkText?: string;
|
||||
type: 'error' | 'warning' | 'log' | 'success'
|
||||
message: string | JSX.Element
|
||||
key?: string
|
||||
jsonData?: any
|
||||
timestring?: string
|
||||
link?: string
|
||||
linkText?: string
|
||||
defaultCollapsed?: boolean
|
||||
}
|
||||
|
||||
export type DeployValue = Record<IFile['name'], any>;
|
||||
export type DeployValue = Record<IFile['name'], any>
|
||||
|
||||
export interface IState {
|
||||
files: IFile[];
|
||||
gistId?: string | null;
|
||||
gistOwner?: string | null;
|
||||
gistName?: string | null;
|
||||
active: number;
|
||||
activeWat: number;
|
||||
loading: boolean;
|
||||
gistLoading: boolean;
|
||||
zipLoading: boolean;
|
||||
compiling: boolean;
|
||||
logs: ILog[];
|
||||
deployLogs: ILog[];
|
||||
transactionLogs: ILog[];
|
||||
scriptLogs: ILog[];
|
||||
editorCtx?: typeof monaco.editor;
|
||||
files: IFile[]
|
||||
gistId?: string | null
|
||||
gistOwner?: string | null
|
||||
gistName?: string | null
|
||||
active: number
|
||||
activeWat: number
|
||||
loading: boolean
|
||||
gistLoading: boolean
|
||||
zipLoading: boolean
|
||||
compiling: boolean
|
||||
logs: ILog[]
|
||||
deployLogs: ILog[]
|
||||
transactionLogs: ILog[]
|
||||
scriptLogs: ILog[]
|
||||
editorCtx?: typeof monaco.editor
|
||||
editorSettings: {
|
||||
tabSize: number;
|
||||
};
|
||||
tabSize: number
|
||||
}
|
||||
splits: {
|
||||
[id: string]: SplitSize
|
||||
};
|
||||
client: XrplClient | null;
|
||||
clientStatus: "offline" | "online";
|
||||
mainModalOpen: boolean;
|
||||
mainModalShowed: boolean;
|
||||
accounts: IAccount[];
|
||||
}
|
||||
client: XrplClient | null
|
||||
clientStatus: 'offline' | 'online'
|
||||
mainModalOpen: boolean
|
||||
mainModalShowed: boolean
|
||||
accounts: IAccount[]
|
||||
compileOptions: {
|
||||
optimizationLevel: '-O0' | '-O1' | '-O2' | '-O3' | '-O4' | '-Os';
|
||||
optimizationLevel: '-O0' | '-O1' | '-O2' | '-O3' | '-O4' | '-Os'
|
||||
strip: boolean
|
||||
},
|
||||
}
|
||||
deployValues: DeployValue
|
||||
}
|
||||
|
||||
@@ -110,11 +110,11 @@ let initialState: IState = {
|
||||
gistLoading: false,
|
||||
zipLoading: false,
|
||||
editorSettings: {
|
||||
tabSize: 2,
|
||||
tabSize: 2
|
||||
},
|
||||
splits: {},
|
||||
client: null,
|
||||
clientStatus: "offline" as "offline",
|
||||
clientStatus: 'offline' as 'offline',
|
||||
mainModalOpen: false,
|
||||
mainModalShowed: false,
|
||||
accounts: [],
|
||||
@@ -123,23 +123,23 @@ let initialState: IState = {
|
||||
strip: true
|
||||
},
|
||||
deployValues: {}
|
||||
};
|
||||
}
|
||||
|
||||
let localStorageAccounts: string | null = null;
|
||||
let initialAccounts: IAccount[] = [];
|
||||
let localStorageAccounts: string | null = null
|
||||
let initialAccounts: IAccount[] = []
|
||||
|
||||
// TODO: What exactly should we store in localStorage? editorSettings, splits, accounts?
|
||||
|
||||
// Check if there's a persited accounts in localStorage
|
||||
if (typeof window !== "undefined") {
|
||||
if (typeof window !== 'undefined') {
|
||||
try {
|
||||
localStorageAccounts = localStorage.getItem("hooksIdeAccounts");
|
||||
localStorageAccounts = localStorage.getItem('hooksIdeAccounts')
|
||||
} catch (err) {
|
||||
console.log(`localStorage state broken`);
|
||||
localStorage.removeItem("hooksIdeAccounts");
|
||||
console.log(`localStorage state broken`)
|
||||
localStorage.removeItem('hooksIdeAccounts')
|
||||
}
|
||||
if (localStorageAccounts) {
|
||||
initialAccounts = JSON.parse(localStorageAccounts);
|
||||
initialAccounts = JSON.parse(localStorageAccounts)
|
||||
}
|
||||
// filter out old accounts (they do not have version property at all)
|
||||
// initialAccounts = initialAccounts.filter(acc => acc.version === '2');
|
||||
@@ -149,36 +149,35 @@ if (typeof window !== "undefined") {
|
||||
const state = proxy<IState>({
|
||||
...initialState,
|
||||
accounts: initialAccounts.length > 0 ? initialAccounts : [],
|
||||
logs: [],
|
||||
});
|
||||
logs: []
|
||||
})
|
||||
// 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}`)
|
||||
|
||||
client.on("online", () => {
|
||||
state.client = ref(client);
|
||||
state.clientStatus = "online";
|
||||
});
|
||||
client.on('online', () => {
|
||||
state.client = ref(client)
|
||||
state.clientStatus = 'online'
|
||||
})
|
||||
|
||||
client.on("offline", () => {
|
||||
state.clientStatus = "offline";
|
||||
});
|
||||
client.on('offline', () => {
|
||||
state.clientStatus = 'offline'
|
||||
})
|
||||
|
||||
if (process.env.NODE_ENV !== "production") {
|
||||
devtools(state, "Files State");
|
||||
if (process.env.NODE_ENV !== 'production') {
|
||||
devtools(state, 'Files State')
|
||||
}
|
||||
|
||||
if (typeof window !== "undefined") {
|
||||
if (typeof window !== 'undefined') {
|
||||
subscribe(state.accounts, () => {
|
||||
const { accounts } = state;
|
||||
const { accounts } = state
|
||||
const accountsNoLoading = accounts.map(acc => ({ ...acc, isLoading: false }))
|
||||
localStorage.setItem("hooksIdeAccounts", JSON.stringify(accountsNoLoading));
|
||||
});
|
||||
localStorage.setItem('hooksIdeAccounts', JSON.stringify(accountsNoLoading))
|
||||
})
|
||||
|
||||
const updateActiveWat = () => {
|
||||
const filename = state.files[state.active]?.name
|
||||
|
||||
const compiledFiles = state.files.filter(
|
||||
file => file.compiledContent)
|
||||
const compiledFiles = state.files.filter(file => file.compiledContent)
|
||||
const idx = compiledFiles.findIndex(file => file.name === filename)
|
||||
|
||||
if (idx !== -1) state.activeWat = idx
|
||||
|
||||
@@ -1,260 +1,252 @@
|
||||
import { proxy } from 'valtio';
|
||||
import { deepEqual } from '../utils/object';
|
||||
import transactionsData from "../content/transactions.json";
|
||||
import state from '.';
|
||||
import { showAlert } from "../state/actions/showAlert";
|
||||
import { parseJSON } from '../utils/json';
|
||||
import { proxy } from 'valtio'
|
||||
import { deepEqual } from '../utils/object'
|
||||
import transactionsData from '../content/transactions.json'
|
||||
import state from '.'
|
||||
import { showAlert } from '../state/actions/showAlert'
|
||||
import { parseJSON } from '../utils/json'
|
||||
|
||||
export type SelectOption = {
|
||||
value: string;
|
||||
label: string;
|
||||
};
|
||||
|
||||
export interface TransactionState {
|
||||
selectedTransaction: SelectOption | null;
|
||||
selectedAccount: SelectOption | null;
|
||||
selectedDestAccount: SelectOption | null;
|
||||
txIsLoading: boolean;
|
||||
txIsDisabled: boolean;
|
||||
txFields: TxFields;
|
||||
viewType: 'json' | 'ui',
|
||||
editorValue?: string,
|
||||
estimatedFee?: string
|
||||
value: string
|
||||
label: string
|
||||
}
|
||||
|
||||
export interface TransactionState {
|
||||
selectedTransaction: SelectOption | null
|
||||
selectedAccount: SelectOption | null
|
||||
selectedDestAccount: SelectOption | null
|
||||
txIsLoading: boolean
|
||||
txIsDisabled: boolean
|
||||
txFields: TxFields
|
||||
viewType: 'json' | 'ui'
|
||||
editorValue?: string
|
||||
estimatedFee?: string
|
||||
}
|
||||
|
||||
export type TxFields = Omit<
|
||||
Partial<typeof transactionsData[0]>,
|
||||
"Account" | "Sequence" | "TransactionType"
|
||||
>;
|
||||
Partial<typeof transactionsData[0]>,
|
||||
'Account' | 'Sequence' | 'TransactionType'
|
||||
>
|
||||
|
||||
export const defaultTransaction: TransactionState = {
|
||||
selectedTransaction: null,
|
||||
selectedAccount: null,
|
||||
selectedDestAccount: null,
|
||||
txIsLoading: false,
|
||||
txIsDisabled: false,
|
||||
txFields: {},
|
||||
viewType: 'ui'
|
||||
};
|
||||
selectedTransaction: null,
|
||||
selectedAccount: null,
|
||||
selectedDestAccount: null,
|
||||
txIsLoading: false,
|
||||
txIsDisabled: false,
|
||||
txFields: {},
|
||||
viewType: 'ui'
|
||||
}
|
||||
|
||||
export const transactionsState = proxy({
|
||||
transactions: [
|
||||
{
|
||||
header: "test1.json",
|
||||
state: { ...defaultTransaction },
|
||||
},
|
||||
],
|
||||
activeHeader: "test1.json"
|
||||
});
|
||||
transactions: [
|
||||
{
|
||||
header: 'test1.json',
|
||||
state: { ...defaultTransaction }
|
||||
}
|
||||
],
|
||||
activeHeader: 'test1.json'
|
||||
})
|
||||
|
||||
export const renameTxState = (oldName: string, nwName: string) => {
|
||||
const tx = transactionsState.transactions.find(tx => tx.header === oldName);
|
||||
const tx = transactionsState.transactions.find(tx => tx.header === oldName)
|
||||
|
||||
if (!tx) throw Error(`No transaction state exists with given header name ${oldName}`);
|
||||
if (!tx) throw Error(`No transaction state exists with given header name ${oldName}`)
|
||||
|
||||
tx.header = nwName
|
||||
tx.header = nwName
|
||||
}
|
||||
|
||||
/**
|
||||
* Simple transaction state changer
|
||||
* @param header Unique key and tab name for the transaction tab
|
||||
* @param partialTx partial transaction state, `undefined` deletes the transaction
|
||||
*
|
||||
*
|
||||
*/
|
||||
export const modifyTxState = (
|
||||
header: string,
|
||||
partialTx?: Partial<TransactionState>,
|
||||
opts: { replaceState?: boolean } = {}
|
||||
header: string,
|
||||
partialTx?: Partial<TransactionState>,
|
||||
opts: { replaceState?: boolean } = {}
|
||||
) => {
|
||||
const tx = transactionsState.transactions.find(tx => tx.header === header);
|
||||
const tx = transactionsState.transactions.find(tx => tx.header === header)
|
||||
|
||||
if (partialTx === undefined) {
|
||||
transactionsState.transactions = transactionsState.transactions.filter(
|
||||
tx => tx.header !== header
|
||||
);
|
||||
return;
|
||||
if (partialTx === undefined) {
|
||||
transactionsState.transactions = transactionsState.transactions.filter(
|
||||
tx => tx.header !== header
|
||||
)
|
||||
return
|
||||
}
|
||||
|
||||
if (!tx) {
|
||||
const state = {
|
||||
...defaultTransaction,
|
||||
...partialTx
|
||||
}
|
||||
transactionsState.transactions.push({
|
||||
header,
|
||||
state
|
||||
})
|
||||
return state
|
||||
}
|
||||
|
||||
if (!tx) {
|
||||
const state = {
|
||||
...defaultTransaction,
|
||||
...partialTx,
|
||||
}
|
||||
transactionsState.transactions.push({
|
||||
header,
|
||||
state,
|
||||
});
|
||||
return state;
|
||||
if (opts.replaceState) {
|
||||
const repTx: TransactionState = {
|
||||
...defaultTransaction,
|
||||
...partialTx
|
||||
}
|
||||
tx.state = repTx
|
||||
return repTx
|
||||
}
|
||||
|
||||
if (opts.replaceState) {
|
||||
const repTx: TransactionState = {
|
||||
...defaultTransaction,
|
||||
...partialTx,
|
||||
}
|
||||
tx.state = repTx
|
||||
return repTx
|
||||
}
|
||||
Object.keys(partialTx).forEach(k => {
|
||||
// Typescript mess here, but is definitely safe!
|
||||
const s = tx.state as any
|
||||
const p = partialTx as any // ? Make copy
|
||||
if (!deepEqual(s[k], p[k])) s[k] = p[k]
|
||||
})
|
||||
|
||||
Object.keys(partialTx).forEach(k => {
|
||||
// Typescript mess here, but is definitely safe!
|
||||
const s = tx.state as any;
|
||||
const p = partialTx as any; // ? Make copy
|
||||
if (!deepEqual(s[k], p[k])) s[k] = p[k];
|
||||
});
|
||||
|
||||
return tx.state
|
||||
};
|
||||
return tx.state
|
||||
}
|
||||
|
||||
// state to tx options
|
||||
export const prepareTransaction = (data: any) => {
|
||||
let options = { ...data };
|
||||
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;
|
||||
} else {
|
||||
options[field] = undefined; // 👇 💀
|
||||
}
|
||||
}
|
||||
// handle type: `json`
|
||||
if (_value && typeof _value === "object" && _value.$type === "json") {
|
||||
if (typeof _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
|
||||
}
|
||||
}
|
||||
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
|
||||
} else {
|
||||
options[field] = undefined // 👇 💀
|
||||
}
|
||||
}
|
||||
// handle type: `json`
|
||||
if (_value && typeof _value === 'object' && _value.$type === 'json') {
|
||||
if (typeof _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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// delete unnecessary fields
|
||||
if (!options[field]) {
|
||||
delete options[field];
|
||||
}
|
||||
});
|
||||
// delete unnecessary fields
|
||||
if (!options[field]) {
|
||||
delete options[field]
|
||||
}
|
||||
})
|
||||
|
||||
return options
|
||||
return options
|
||||
}
|
||||
|
||||
// editor value to state
|
||||
export const prepareState = (value: string, transactionType?: string) => {
|
||||
const options = parseJSON(value);
|
||||
if (!options) {
|
||||
showAlert("Error!", {
|
||||
body: "Cannot save editor with malformed transaction."
|
||||
})
|
||||
return
|
||||
};
|
||||
const options = parseJSON(value)
|
||||
if (!options) {
|
||||
showAlert('Error!', {
|
||||
body: 'Cannot save editor with malformed transaction.'
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
const { Account, TransactionType, Destination, ...rest } = options;
|
||||
let tx: Partial<TransactionState> = {};
|
||||
const schema = getTxFields(transactionType)
|
||||
const { Account, TransactionType, Destination, ...rest } = options
|
||||
let tx: Partial<TransactionState> = {}
|
||||
const schema = getTxFields(transactionType)
|
||||
|
||||
if (Account) {
|
||||
const acc = state.accounts.find(acc => acc.address === Account);
|
||||
if (acc) {
|
||||
tx.selectedAccount = {
|
||||
label: acc.name,
|
||||
value: acc.address,
|
||||
};
|
||||
} else {
|
||||
tx.selectedAccount = {
|
||||
label: Account,
|
||||
value: Account,
|
||||
};
|
||||
}
|
||||
if (Account) {
|
||||
const acc = state.accounts.find(acc => acc.address === Account)
|
||||
if (acc) {
|
||||
tx.selectedAccount = {
|
||||
label: acc.name,
|
||||
value: acc.address
|
||||
}
|
||||
} else {
|
||||
tx.selectedAccount = null;
|
||||
tx.selectedAccount = {
|
||||
label: Account,
|
||||
value: Account
|
||||
}
|
||||
}
|
||||
} else {
|
||||
tx.selectedAccount = null
|
||||
}
|
||||
|
||||
if (TransactionType) {
|
||||
tx.selectedTransaction = {
|
||||
label: TransactionType,
|
||||
value: TransactionType,
|
||||
};
|
||||
if (TransactionType) {
|
||||
tx.selectedTransaction = {
|
||||
label: TransactionType,
|
||||
value: TransactionType
|
||||
}
|
||||
} else {
|
||||
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.selectedTransaction = null;
|
||||
tx.selectedDestAccount = null
|
||||
}
|
||||
} else if (Destination) {
|
||||
rest.Destination = Destination
|
||||
}
|
||||
|
||||
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
|
||||
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) {
|
||||
rest[field] = {
|
||||
$type: 'xrp',
|
||||
$value: +value / 1000000 // ! maybe use bigint?
|
||||
}
|
||||
} else if (typeof value === 'object') {
|
||||
rest[field] = {
|
||||
$type: 'json',
|
||||
$value: value
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
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) {
|
||||
rest[field] = {
|
||||
$type: "xrp",
|
||||
$value: +value / 1000000, // ! maybe use bigint?
|
||||
};
|
||||
} else if (typeof value === "object") {
|
||||
rest[field] = {
|
||||
$type: "json",
|
||||
$value: value,
|
||||
};
|
||||
}
|
||||
});
|
||||
tx.txFields = rest
|
||||
|
||||
tx.txFields = rest;
|
||||
|
||||
return tx
|
||||
return tx
|
||||
}
|
||||
|
||||
export const getTxFields = (tt?: string) => {
|
||||
const txFields: TxFields | undefined = transactionsData.find(
|
||||
tx => tx.TransactionType === tt
|
||||
);
|
||||
const txFields: TxFields | undefined = transactionsData.find(tx => tx.TransactionType === tt)
|
||||
|
||||
if (!txFields) return {}
|
||||
if (!txFields) return {}
|
||||
|
||||
let _txFields = Object.keys(txFields)
|
||||
.filter(
|
||||
key => !["TransactionType", "Account", "Sequence"].includes(key)
|
||||
)
|
||||
.reduce<TxFields>(
|
||||
(tf, key) => (
|
||||
(tf[key as keyof TxFields] = (txFields as any)[key]), tf
|
||||
),
|
||||
{}
|
||||
);
|
||||
return _txFields
|
||||
let _txFields = Object.keys(txFields)
|
||||
.filter(key => !['TransactionType', 'Account', 'Sequence'].includes(key))
|
||||
.reduce<TxFields>((tf, key) => ((tf[key as keyof TxFields] = (txFields as any)[key]), tf), {})
|
||||
return _txFields
|
||||
}
|
||||
|
||||
export { transactionsData }
|
||||
|
||||
export const transactionsOptions = transactionsData.map(tx => ({
|
||||
value: tx.TransactionType,
|
||||
label: tx.TransactionType,
|
||||
}));
|
||||
value: tx.TransactionType,
|
||||
label: tx.TransactionType
|
||||
}))
|
||||
|
||||
export const defaultTransactionType = transactionsOptions.find(tt => tt.value === 'Payment')
|
||||
export const defaultTransactionType = transactionsOptions.find(tt => tt.value === 'Payment')
|
||||
|
||||
Reference in New Issue
Block a user