Merge branch 'main' into feat/engine-code-links
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,54 +1,52 @@
|
||||
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 ResultLink from "../../components/ResultLink";
|
||||
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 ResultLink from '../../components/ResultLink'
|
||||
|
||||
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 (
|
||||
@@ -57,30 +55,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
|
||||
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: {
|
||||
@@ -90,79 +87,71 @@ 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
|
||||
})
|
||||
|
||||
const txHash = submitRes.tx_json?.hash;
|
||||
const txHash = submitRes.tx_json?.hash
|
||||
const resultMsg = ref(
|
||||
<>
|
||||
[<ResultLink result={submitRes.engine_result} />]{" "}
|
||||
{submitRes.engine_result_message}{" "}
|
||||
[<ResultLink result={submitRes.engine_result} />] {submitRes.engine_result_message}{' '}
|
||||
{txHash && (
|
||||
<>
|
||||
Transaction hash:{" "}
|
||||
Transaction hash:{' '}
|
||||
<Link
|
||||
as="a"
|
||||
href={`https://${process.env.NEXT_PUBLIC_EXPLORER_URL}/${txHash}`}
|
||||
@@ -174,123 +163,120 @@ export const deployHook = async (
|
||||
</>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
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",
|
||||
message: resultMsg,
|
||||
});
|
||||
type: 'success',
|
||||
message: resultMsg
|
||||
})
|
||||
} else if (submitRes.engine_result) {
|
||||
state.deployLogs.push({
|
||||
type: "error",
|
||||
message: resultMsg,
|
||||
});
|
||||
type: 'error',
|
||||
message: resultMsg
|
||||
})
|
||||
} else {
|
||||
state.deployLogs.push({
|
||||
type: "error",
|
||||
message: `[${submitRes.error}] ${submitRes.error_exception}`,
|
||||
});
|
||||
type: 'error',
|
||||
message: `[${submitRes.error}] ${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,117 +1,99 @@
|
||||
import { Octokit } from "@octokit/core";
|
||||
import Router from "next/router";
|
||||
import state from '../index';
|
||||
import { templateFileIds } from '../constants';
|
||||
import { Octokit } from '@octokit/core'
|
||||
import state, { IFile } 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
|
||||
/**
|
||||
* Fetches files from Github Gists based on gistId and stores them in global state
|
||||
*/
|
||||
export const fetchFiles = (gistId: string) => {
|
||||
state.loading = true;
|
||||
if (gistId && !state.files.length) {
|
||||
export const fetchFiles = async (gistId: string) => {
|
||||
if (!gistId || state.files.length) return
|
||||
|
||||
state.loading = true
|
||||
state.logs.push({
|
||||
type: 'log',
|
||||
message: `Fetching Gist with id: ${gistId}`
|
||||
})
|
||||
try {
|
||||
const res = await octokit.request('GET /gists/{gist_id}', { gist_id: gistId })
|
||||
|
||||
const isTemplate = (id: string) =>
|
||||
Object.values(templateFileIds)
|
||||
.map(v => v.id)
|
||||
.includes(id)
|
||||
|
||||
if (isTemplate(gistId)) {
|
||||
// fetch headers
|
||||
const headerRes = await fetch(
|
||||
`${process.env.NEXT_PUBLIC_COMPILE_API_BASE_URL}/api/header-files`
|
||||
)
|
||||
if (!headerRes.ok) throw Error('Failed to fetch headers')
|
||||
|
||||
const headerJson = await headerRes.json()
|
||||
const headerFiles: Record<string, { filename: string; content: string; language: string }> =
|
||||
{}
|
||||
Object.entries(headerJson).forEach(([key, value]) => {
|
||||
const fname = `${key}.h`
|
||||
headerFiles[fname] = { filename: fname, content: value as string, language: 'C' }
|
||||
})
|
||||
const files = {
|
||||
...res.data.files,
|
||||
...headerFiles
|
||||
}
|
||||
res.data.files = files
|
||||
}
|
||||
|
||||
if (!res.data.files) throw Error('No files could be fetched from given gist id!')
|
||||
|
||||
const files: IFile[] = Object.keys(res.data.files).map(filename => ({
|
||||
name: res.data.files?.[filename]?.filename || 'untitled.c',
|
||||
language: res.data.files?.[filename]?.language?.toLowerCase() || '',
|
||||
content: res.data.files?.[filename]?.content || ''
|
||||
}))
|
||||
|
||||
files.sort((a, b) => {
|
||||
const aBasename = a.name.split('.')?.[0]
|
||||
const aExt = a.name.split('.').pop() || ''
|
||||
const bBasename = b.name.split('.')?.[0]
|
||||
const bExt = b.name.split('.').pop() || ''
|
||||
|
||||
// default priority is undefined == 0
|
||||
const extPriority: Record<string, number> = {
|
||||
c: 3,
|
||||
md: 2,
|
||||
h: -1
|
||||
}
|
||||
|
||||
// Sort based on extention priorities
|
||||
const comp = (extPriority[bExt] || 0) - (extPriority[aExt] || 0)
|
||||
if (comp !== 0) return comp
|
||||
|
||||
// Otherwise fallback to alphabetical sorting
|
||||
return aBasename.localeCompare(bBasename)
|
||||
})
|
||||
|
||||
state.logs.push({
|
||||
type: "log",
|
||||
message: `Fetching Gist with id: ${gistId}`,
|
||||
});
|
||||
type: 'success',
|
||||
message: 'Fetched successfully ✅'
|
||||
})
|
||||
state.files = files
|
||||
state.gistId = gistId
|
||||
state.gistOwner = res.data.owner?.login
|
||||
|
||||
octokit
|
||||
.request("GET /gists/{gist_id}", { gist_id: gistId })
|
||||
.then(async res => {
|
||||
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`);
|
||||
if (resHeader.ok) {
|
||||
const resHeaderJson = await resHeader.json()
|
||||
const headerFiles: Record<string, { filename: string; content: string; language: string }> = {};
|
||||
Object.entries(resHeaderJson).forEach(([key, value]) => {
|
||||
const fname = `${key}.h`;
|
||||
headerFiles[fname] = { filename: fname, content: value as string, language: 'C' }
|
||||
})
|
||||
const files = {
|
||||
...res.data.files,
|
||||
...headerFiles
|
||||
};
|
||||
res.data.files = files;
|
||||
}
|
||||
} catch (err) {
|
||||
console.log(err)
|
||||
}
|
||||
|
||||
|
||||
|
||||
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 }
|
||||
// console.log(headerFiles)
|
||||
// res.data.files = files
|
||||
// return 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 || "",
|
||||
}));
|
||||
// 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');
|
||||
// If a has c extension and b doesn't move a up
|
||||
if (aCext && !bCext) {
|
||||
return -1;
|
||||
}
|
||||
if (!aCext && bCext) {
|
||||
return 1
|
||||
}
|
||||
// Otherwise fallback to default sorting based on basename
|
||||
if (aBasename > bBasename) {
|
||||
return 1;
|
||||
}
|
||||
if (bBasename > aBasename) {
|
||||
return -1;
|
||||
}
|
||||
return 0;
|
||||
})
|
||||
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;
|
||||
} else {
|
||||
// Open main modal if now files
|
||||
state.mainModalOpen = true;
|
||||
}
|
||||
return Router.push({ pathname: "/develop" });
|
||||
}
|
||||
state.loading = false;
|
||||
})
|
||||
.catch((err) => {
|
||||
// console.error(err)
|
||||
state.loading = false;
|
||||
state.logs.push({
|
||||
type: "error",
|
||||
message: `Couldn't find Gist with id: ${gistId}`,
|
||||
});
|
||||
return;
|
||||
});
|
||||
return;
|
||||
const gistName =
|
||||
files.find(file => file.language === 'c' || file.language === 'javascript')?.name ||
|
||||
'untitled'
|
||||
state.gistName = gistName
|
||||
} catch (err) {
|
||||
console.error(err)
|
||||
let message: string
|
||||
if (err instanceof Error) message = err.message
|
||||
else message = `Something went wrong, try again later!`
|
||||
state.logs.push({
|
||||
type: 'error',
|
||||
message: `Error: ${message}`
|
||||
})
|
||||
}
|
||||
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() || ''
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
66
state/actions/sendTransaction.ts
Normal file
66
state/actions/sendTransaction.ts
Normal file
@@ -0,0 +1,66 @@
|
||||
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`
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -1,19 +1,19 @@
|
||||
import { derive, sign } from "xrpl-accountlib";
|
||||
import { derive, sign } from 'xrpl-accountlib'
|
||||
|
||||
import state from "..";
|
||||
import type { IAccount } from "..";
|
||||
import ResultLink from "../../components/ResultLink";
|
||||
import { ref } from "valtio";
|
||||
import state from '..'
|
||||
import type { IAccount } from '..'
|
||||
import ResultLink from '../../components/ResultLink'
|
||||
import { ref } from 'valtio'
|
||||
|
||||
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 (
|
||||
@@ -21,58 +21,57 @@ export const sendTransaction = async (
|
||||
txOptions: TransactionOptions,
|
||||
options?: OtherOptions
|
||||
) => {
|
||||
if (!state.client) throw Error("XRPL client not initalized");
|
||||
if (!state.client) throw Error('XRPL client not initalized')
|
||||
|
||||
const { Fee = "1000", ...opts } = txOptions;
|
||||
const { Fee = '1000', ...opts } = txOptions
|
||||
const tx: TransactionOptions = {
|
||||
Account: account.address,
|
||||
Sequence: account.sequence,
|
||||
Fee, // TODO auto-fillable default
|
||||
...opts,
|
||||
};
|
||||
const { logPrefix = "" } = options || {};
|
||||
...opts
|
||||
}
|
||||
const { logPrefix = '' } = options || {}
|
||||
try {
|
||||
const signedAccount = derive.familySeed(account.secret);
|
||||
const { signedTransaction } = sign(tx, signedAccount);
|
||||
const signedAccount = derive.familySeed(account.secret)
|
||||
const { signedTransaction } = sign(tx, signedAccount)
|
||||
const response = await state.client.send({
|
||||
command: "submit",
|
||||
tx_blob: signedTransaction,
|
||||
});
|
||||
command: 'submit',
|
||||
tx_blob: signedTransaction
|
||||
})
|
||||
|
||||
const resultMsg = ref(
|
||||
<>
|
||||
{logPrefix}[<ResultLink result={response.engine_result} />]{" "}
|
||||
{response.engine_result_message}
|
||||
{logPrefix}[<ResultLink result={response.engine_result} />] {response.engine_result_message}
|
||||
</>
|
||||
);
|
||||
if (response.engine_result === "tesSUCCESS") {
|
||||
)
|
||||
if (response.engine_result === 'tesSUCCESS') {
|
||||
state.transactionLogs.push({
|
||||
type: "success",
|
||||
message: resultMsg,
|
||||
});
|
||||
type: 'success',
|
||||
message: resultMsg
|
||||
})
|
||||
} else if (response.engine_result) {
|
||||
state.transactionLogs.push({
|
||||
type: "error",
|
||||
message: resultMsg,
|
||||
});
|
||||
type: 'error',
|
||||
message: resultMsg
|
||||
})
|
||||
} else {
|
||||
state.transactionLogs.push({
|
||||
type: "error",
|
||||
message: `${logPrefix}[${response.error}] ${response.error_exception}`,
|
||||
});
|
||||
type: 'error',
|
||||
message: `${logPrefix}[${response.error}] ${response.error_exception}`
|
||||
})
|
||||
}
|
||||
const currAcc = state.accounts.find(acc => acc.address === account.address);
|
||||
const currAcc = state.accounts.find(acc => acc.address === account.address)
|
||||
if (currAcc && response.account_sequence_next) {
|
||||
currAcc.sequence = response.account_sequence_next;
|
||||
currAcc.sequence = response.account_sequence_next
|
||||
}
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
console.error(err)
|
||||
state.transactionLogs.push({
|
||||
type: "error",
|
||||
type: 'error',
|
||||
message:
|
||||
err instanceof Error
|
||||
? `${logPrefix}Error: ${err.message}`
|
||||
: `${logPrefix}Something went wrong, try again later`,
|
||||
});
|
||||
: `${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)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user