Merge branch 'main' into fix/renaming-ext
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,21 +1,21 @@
 | 
			
		||||
import { getFileExtention } from '../../utils/helpers';
 | 
			
		||||
import state, { IFile } from '../index';
 | 
			
		||||
import { getFileExtention } from '../../utils/helpers'
 | 
			
		||||
import state, { IFile } from '../index'
 | 
			
		||||
 | 
			
		||||
const languageMapping: Record<string, string | undefined> = {
 | 
			
		||||
  'ts': 'typescript',
 | 
			
		||||
  'js': 'javascript',
 | 
			
		||||
  'md': 'markdown',
 | 
			
		||||
  'c': 'c',
 | 
			
		||||
  'h': 'c',
 | 
			
		||||
  ts: 'typescript',
 | 
			
		||||
  js: 'javascript',
 | 
			
		||||
  md: 'markdown',
 | 
			
		||||
  c: 'c',
 | 
			
		||||
  h: 'c'
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export const createNewFile = (name: string) => {
 | 
			
		||||
  const ext = getFileExtention(name) || ''
 | 
			
		||||
 | 
			
		||||
  const emptyFile: IFile = { name, language: languageMapping[ext] || '', content: '' };
 | 
			
		||||
  state.files.push(emptyFile);
 | 
			
		||||
  state.active = state.files.length - 1;
 | 
			
		||||
};
 | 
			
		||||
  const emptyFile: IFile = { name, language: languageMapping[ext] || '', 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)
 | 
			
		||||
@@ -25,4 +25,4 @@ export const renameFile = (oldName: string, nwName: string) => {
 | 
			
		||||
  const language = languageMapping[ext] || ''
 | 
			
		||||
  file.name = nwName
 | 
			
		||||
  file.language = language
 | 
			
		||||
};
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -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,57 +1,62 @@
 | 
			
		||||
import { Octokit } from "@octokit/core";
 | 
			
		||||
import state, { IFile } 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 files from Github Gists based on gistId and stores them in global state
 | 
			
		||||
 */
 | 
			
		||||
export const fetchFiles = async (gistId: string) => {
 | 
			
		||||
  if (!gistId || state.files.length) return
 | 
			
		||||
 | 
			
		||||
  state.loading = true;
 | 
			
		||||
  state.loading = true
 | 
			
		||||
  state.logs.push({
 | 
			
		||||
    type: "log",
 | 
			
		||||
    message: `Fetching Gist with id: ${gistId}`,
 | 
			
		||||
  });
 | 
			
		||||
    type: 'log',
 | 
			
		||||
    message: `Fetching Gist with id: ${gistId}`
 | 
			
		||||
  })
 | 
			
		||||
  try {
 | 
			
		||||
    const res = await octokit
 | 
			
		||||
      .request("GET /gists/{gist_id}", { gist_id: gistId })
 | 
			
		||||
    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)
 | 
			
		||||
    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 headerRes = await fetch(
 | 
			
		||||
        `${process.env.NEXT_PUBLIC_COMPILE_API_BASE_URL}/api/header-files`
 | 
			
		||||
      )
 | 
			
		||||
      if (!headerRes.ok) throw Error('Failed to fetch headers')
 | 
			
		||||
 | 
			
		||||
      const headerJson = await headerRes.json()
 | 
			
		||||
      const headerFiles: Record<string, { filename: string; content: string; language: string }> = {};
 | 
			
		||||
      const headerFiles: Record<string, { filename: string; content: string; language: string }> =
 | 
			
		||||
        {}
 | 
			
		||||
      Object.entries(headerJson).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
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    if (!res.data.files) throw Error("No files could be fetched from given gist id!")
 | 
			
		||||
    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 || "",
 | 
			
		||||
    }));
 | 
			
		||||
    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() || '';
 | 
			
		||||
      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> = {
 | 
			
		||||
@@ -63,20 +68,22 @@ export const fetchFiles = async (gistId: string) => {
 | 
			
		||||
      // 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: "success",
 | 
			
		||||
      message: "Fetched successfully ✅",
 | 
			
		||||
    });
 | 
			
		||||
    state.files = files;
 | 
			
		||||
    state.gistId = gistId;
 | 
			
		||||
    state.gistOwner = res.data.owner?.login;
 | 
			
		||||
      type: 'success',
 | 
			
		||||
      message: 'Fetched successfully ✅'
 | 
			
		||||
    })
 | 
			
		||||
    state.files = files
 | 
			
		||||
    state.gistId = gistId
 | 
			
		||||
    state.gistOwner = res.data.owner?.login
 | 
			
		||||
 | 
			
		||||
    const gistName = files.find(file => file.language === 'c' || file.language === 'javascript')?.name || "untitled";
 | 
			
		||||
    const gistName =
 | 
			
		||||
      files.find(file => file.language === 'c' || file.language === 'javascript')?.name ||
 | 
			
		||||
      'untitled'
 | 
			
		||||
    state.gistName = gistName
 | 
			
		||||
  } catch (err) {
 | 
			
		||||
    console.error(err)
 | 
			
		||||
@@ -84,9 +91,9 @@ export const fetchFiles = async (gistId: string) => {
 | 
			
		||||
    if (err instanceof Error) message = err.message
 | 
			
		||||
    else message = `Something went wrong, try again later!`
 | 
			
		||||
    state.logs.push({
 | 
			
		||||
      type: "error",
 | 
			
		||||
      message: `Error: ${message}`,
 | 
			
		||||
    });
 | 
			
		||||
      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() || ''
 | 
			
		||||
    }
 | 
			
		||||
  })
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -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: '1f8109c80f504e6326db2735df2f0ad6', // Forked
 | 
			
		||||
        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: '953662b22d065449f8ab6f69bc2afe41',  // Forked
 | 
			
		||||
        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: '1f8109c80f504e6326db2735df2f0ad6', // Forked
 | 
			
		||||
    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: '953662b22d065449f8ab6f69bc2afe41', // Forked
 | 
			
		||||
    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