<!doctype html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <title>WASM Example</title>
  </head>
  <body>
    <label for="accountFileInput" class="custom-file-upload">
      Choose Account File
    </label>
    <input type="file" id="accountFileInput" style="display: none" />
    <label for="noteFileInput" class="custom-file-upload">
      Choose Note File
    </label>
    <input type="file" id="noteFileInput" style="display: none" />
    <script type="module" src="./dist/index.js"></script>
    <script type="module">
      // Example of using the exported WebClient in the browser
      import {
        AdviceMap,
        AccountStorageMode,
        AuthSecretKey,
        Felt,
        FeltArray,
        FungibleAsset,
        InputNoteState,
        Note,
        NoteAssets,
        NoteExecutionHint,
        NoteExecutionMode,
        NoteFilter,
        NoteId,
        NoteIdAndArgs,
        NoteIdAndArgsArray,
        NoteInputs,
        NoteMetadata,
        NoteRecipient,
        NoteTag,
        NoteType,
        OutputNote,
        OutputNotesArray,
        Rpo256,
        TransactionRequest,
        TransactionRequestBuilder,
        TransactionScriptInputPair,
        TransactionScriptInputPairArray,
        WebClient,
      } from "./dist/index.js";

      document
        .getElementById("accountFileInput")
        .addEventListener("change", function (event) {
          const file = event.target.files[0];
          if (file) {
            const reader = new FileReader();

            reader.onload = async function (e) {
              let webClient = await createMidenWebClient();
              const arrayBuffer = e.target.result;
              const byteArray = new Uint8Array(arrayBuffer);

              await testImportAccount(webClient, byteArray);
            };

            reader.readAsArrayBuffer(file);
          }
        });

      document
        .getElementById("noteFileInput")
        .addEventListener("change", async function (event) {
          const file = event.target.files[0];
          if (file) {
            const reader = new FileReader();

            reader.onload = async function (e) {
              let webClient = await createMidenWebClient();
              const arrayBuffer = e.target.result;
              const byteArray = new Uint8Array(arrayBuffer);

              await importInputNote(webClient, byteArray, true);
            };

            reader.readAsArrayBuffer(file);
          }
        });

      function setupNoteFileInputListener(webClient) {
        document
          .getElementById("noteFileInput")
          .addEventListener("change", async function (event) {
            const file = event.target.files[0];
            if (file) {
              try {
                const byteArray = await readFileAsByteArray(file);
                console.log(byteArray); // Output the byte array to check the content
                let result = await importInputNote(webClient, byteArray);
                console.log(result); // Log the result of the import process
              } catch (error) {
                console.error("Error handling file:", error);
              }
            }
          });
      }

      async function readFileAsByteArray(file) {
        return new Promise((resolve, reject) => {
          const reader = new FileReader();

          reader.onload = () => {
            const arrayBuffer = reader.result;
            const byteArray = new Uint8Array(arrayBuffer);
            console.log("Byte array length:", byteArray.length); // Check the length
            resolve(byteArray);
          };

          reader.onerror = () => {
            console.error("File read error:", reader.error);
            reject(reader.error);
          };

          reader.readAsArrayBuffer(file);
        });
      }

      async function createMidenWebClient(dbName = "MidenClientDB", initSeed) {
        try {
          let rpc_url = "http://localhost:57291";
          let envoy_proxy_url = "http://localhost:8080";
          const webClient = new WebClient();
          await webClient.create_client(rpc_url, null, initSeed);
          return webClient;
        } catch (error) {
          console.error("Failed to create client with web store:", error);
        }
      }

      async function testStoreAndRpc(webClient) {
        try {
          await webClient.test_store_and_rpc();
        } catch (error) {
          console.error("Failed to create client with web store:", error);
        }
      }

      // Account Functions
      ///////////////////////////////////////////////////////////////////

      async function createNewWallet(webClient, storageMode, mutable) {
        try {
          let result = await webClient.new_wallet(storageMode, mutable);
          console.log(`Created new wallet account with id ${result}`);
          return result;
        } catch (error) {
          console.error("Failed to call create account:", error);
        }
      }

      async function createNewFaucet(
        webClient,
        storageMode,
        nonFungible,
        tokenSymbol,
        decimals,
        maxSupply
      ) {
        try {
          let result = await webClient.new_faucet(
            storageMode,
            nonFungible,
            tokenSymbol,
            decimals,
            maxSupply
          );
          console.log(`Created new faucet with id ${result}`);
          return result;
        } catch (error) {
          console.error("Failed to call create account:", error);
        }
      }

      async function importAccount(webClient, accountAsBytes) {
        try {
          let result = await webClient.import_account(accountAsBytes);
          console.log(result);
          return result;
        } catch (error) {
          console.error("Failed to call import account:", error);
        }
      }

      async function getAccounts(webClient) {
        try {
          let accounts = await webClient.get_accounts();
          let accountIds = accounts.map((account) => account.id);
          console.log(accountIds);
          return accountIds;
        } catch (error) {
          console.error("Failed to call get accounts:", error);
        }
      }

      async function getAccount(webClient, accountId) {
        try {
          let result = await webClient.get_account(accountId);
          console.log(result);
          return result;
        } catch (error) {
          console.error("Failed to call get account:", error);
        }
      }

      // Transaction Functions
      ///////////////////////////////////////////////////////////////////

      async function createNewMintTransaction(
        webClient,
        targetAccountId,
        faucetId,
        noteType,
        amount
      ) {
        try {
          let result = await webClient.new_mint_transaction(
            targetAccountId,
            faucetId,
            noteType,
            amount
          );
          console.log(
            `Created new mint transaction with id ${result.transaction_id}`
          );
          console.log(`Output notes created: ${result.created_note_ids}`);
          return result;
        } catch (error) {
          console.error("Failed to call create new mint transaction:", error);
        }
      }

      async function createNewConsumeTransaction(
        webClient,
        accountId,
        listOfNotes
      ) {
        try {
          let result = await webClient.new_consume_transaction(
            accountId,
            listOfNotes
          );
          console.log(
            `Created new consume transaction with id ${result.transaction_id}`
          );
          console.log(`Output notes created: ${result.created_note_ids}`);
          return result;
        } catch (error) {
          console.error(
            "Failed to call create new consume transaction:",
            error
          );
        }
      }

      async function createNewSendTransaction(
        webClient,
        senderAccountId,
        targetAccountId,
        facuetId,
        noteType,
        amount,
        recallHeight
      ) {
        try {
          let result = await webClient.new_send_transaction(
            senderAccountId,
            targetAccountId,
            facuetId,
            noteType,
            amount,
            recallHeight
          );
          console.log(
            `Created new send transaction with id ${result.transaction_id}`
          );
          console.log(`Output notes created: ${result.created_note_ids}`);
          return result;
        } catch (error) {
          console.error("Failed to call create new send transaction:", error);
        }
      }

      async function createNewSwapTransaction(
        webClient,
        senderAccountId,
        offeredAssetFaucetId,
        offeredAssetAmount,
        requestedAssetFaucetId,
        requestedAssetAmount,
        noteType
      ) {
        try {
          let result = await webClient.new_swap_transaction(
            senderAccountId,
            offeredAssetFaucetId,
            offeredAssetAmount,
            requestedAssetFaucetId,
            requestedAssetAmount,
            noteType
          );
          console.log(
            `Created new swap transaction with id ${result.transaction_id}`
          );
          console.log(
            `Output notes created: ${result.expected_output_note_ids}`
          );
          console.log(
            `Expected Partial Notes: ${result.expected_partial_note_ids}`
          );
          console.log(`Payback Note Tag: ${result.payback_note_tag}`);
          return result;
        } catch (error) {
          console.error("Failed to call create new swap transaction:", error);
        }
      }

      async function getTransactions(webClient) {
        try {
          let result = await webClient.get_transactions();
          console.log(result);
          return result;
        } catch (error) {
          console.error("Failed to call get transactions:", error);
        }
      }

      // Note Functions
      ///////////////////////////////////////////////////////////////////

      async function getInputNotes(webClient, status = "All") {
        try {
          let result = await webClient.get_input_notes(status);
          console.log(result);
          return result;
        } catch (error) {
          console.error("Failed to call get input notes:", error);
        }
      }

      async function getInputNote(webClient, noteId) {
        try {
          let result = await webClient.get_input_note(noteId);
          console.log(result);
          return result;
        } catch (error) {
          console.error("Failed to call get input note:", error);
        }
      }

      async function getOutputNotes(webClient, status = "All") {
        try {
          let result = await webClient.get_output_notes(status);
          console.log(result);
          return result;
        } catch (error) {
          console.error("Failed to call get output notes:", error);
        }
      }

      async function getOutputNote(webClient, noteId) {
        try {
          let result = await webClient.get_output_note(noteId);
          console.log(result);
          return result;
        } catch (error) {
          console.error("Failed to call get input note:", error);
        }
      }

      async function importInputNote(webClient, noteAsBytes, verify) {
        try {
          await webClient.import_note(noteAsBytes, verify);
        } catch (error) {
          console.error("Failed to call import input note:", error);
        }
      }

      async function exportNote(webClient, noteId) {
        try {
          let result = await webClient.export_note(noteId, "Partial");
          let byteArray = new Uint8Array(result);
          console.log(byteArray);
          return byteArray;
        } catch (error) {
          console.error("Failed to call export input note:", error);
        }
      }

      // Sync Functions
      ///////////////////////////////////////////////////////////////////

      async function syncState(webClient) {
        try {
          let result = await webClient.sync_state();
          console.log("Synced state to block ", result);
        } catch (error) {
          console.error("Failed to call sync state:", error);
        }
      }

      async function addTag(webClient, noteTag) {
        try {
          let result = await webClient.add_tag(noteTag);
          console.log(result);
        } catch (error) {
          console.error("Failed to call add note tag:", error);
        }
      }

      // Tests
      ///////////////////////////////////////////////////////////////////

      // Done
      async function testCreateNewWallet() {
        const initSeed = new Uint8Array([
          223, 198, 73, 130, 93, 79, 220, 107, 25, 181, 91, 235, 101, 105, 136,
          172, 151, 161, 174, 73, 142, 81, 238, 192, 227, 93, 54, 113, 136, 223,
          75, 174,
        ]);

        console.log("testCreateNewWallet started");
        const webClient = await createMidenWebClient("MidenClientDB", initSeed);
        const wallet = await webClient.new_wallet(
          AccountStorageMode.public(),
          true
        );
        console.log("Wallet id: ");
        console.log(wallet.id().to_string());

        console.log("testCreateNewWallet finished");
      }

      // Done
      async function testCreateNewFaucet() {
        console.log("testCreateNewFaucet started");
        let webClient = await createMidenWebClient();

        await createNewFaucet(
          webClient,
          "Private",
          false,
          "DEN",
          "10",
          "1000000"
        );

        console.log("testCreateNewFaucet finished");
      }

      // Done
      async function testImportAccount(webClient, accountAsBytes) {
        console.log("testImportAccount started");
        await importAccount(webClient, accountAsBytes);
        console.log("testImportAccount finished");
      }

      // Done
      async function testGetAccounts(shouldCreateAccounts = true) {
        console.log("testGetAccounts started");
        let webClient = await createMidenWebClient();
        if (shouldCreateAccounts) {
          await createNewWallet(webClient, "Private", true);
        }

        await getAccounts(webClient);

        console.log("testGetAccounts finished");
      }

      // Done
      async function testGetAccount() {
        console.log("testGetAccount started");

        let webClient = await createMidenWebClient();
        let accountId = await createNewWallet(webClient, "Private", true);

        await getAccount(webClient, accountId.to_string());

        console.log("testGetAccount finished");
      }

      // Done
      async function testNewMintTransaction() {
        console.log("testNewMintTransaction started");

        let webClient = await createMidenWebClient();
        let targetAccount = await createNewWallet(
          webClient,
          AccountStorageMode.private(),
          true
        );
        let faucet = await createNewFaucet(
          webClient,
          AccountStorageMode.private(),
          false,
          "DAG",
          8,
          BigInt(10000000)
        );
        console.log("syncing state...");
        await syncState(webClient);
        console.log("state synced");
        await new Promise((r) => setTimeout(r, 20000));

        await webClient.fetch_and_cache_account_auth_by_pub_key(faucet.id());
        console.log("fetched");
        let result = await createNewMintTransaction(
          webClient,
          targetAccount.id(),
          faucet.id(),
          NoteType.private(),
          BigInt(1000)
        );
        await new Promise((r) => setTimeout(r, 20000));
        await syncState(webClient);

        console.log("testNewMintTransaction finished");
      }

      // Done
      async function testNewConsumeTransaction() {
        console.log("testNewConsumeTransaction started");

        let webClient = await createMidenWebClient();
        let targetAccount = await createNewWallet(
          webClient,
          AccountStorageMode.private(),
          true
        );
        let faucet = await createNewFaucet(
          webClient,
          AccountStorageMode.private(),
          false,
          "DEN",
          10,
          BigInt(1000000)
        );
        await new Promise((r) => setTimeout(r, 20000));
        await syncState(webClient);

        await webClient.fetch_and_cache_account_auth_by_pub_key(faucet.id());
        let mintTransactionResult = await createNewMintTransaction(
          webClient,
          targetAccount.id(),
          faucet.id(),
          NoteType.private(),
          BigInt(1000)
        );

        let created_notes = mintTransactionResult.created_notes().notes();
        let created_note_ids = created_notes.map((note) =>
          note.id().to_string()
        );

        console.log("waiting on minted notes to get eaten by the rpc...");
        await new Promise((r) => setTimeout(r, 20000));
        await syncState(webClient);

        await webClient.fetch_and_cache_account_auth_by_pub_key(
          targetAccount.id()
        );
        let consumeTransactionResult = await createNewConsumeTransaction(
          webClient,
          targetAccount.id(),
          created_note_ids
        );
        await new Promise((r) => setTimeout(r, 20000));
        await syncState(webClient);

        console.log("testNewConsumeTransaction finished");
      }

      // Done
      async function testNewSendTransaction() {
        console.log("testNewSendTransaction started");

        let webClient = await createMidenWebClient();
        let senderAccountId = await createNewWallet(webClient, "Private", true);
        let targetAccountId = await createNewWallet(webClient, "Private", true);
        let faucetId = await createNewFaucet(
          webClient,
          "Private",
          false,
          "DEN",
          "10",
          "1000000"
        );
        await syncState(webClient);
        await new Promise((r) => setTimeout(r, 20000));

        await webClient.fetch_and_cache_account_auth_by_pub_key(
          faucetId.to_string()
        );
        let mintTransactionResult = await createNewMintTransaction(
          webClient,
          senderAccountId.to_string(),
          faucetId.to_string(),
          "Private",
          "1000"
        );
        await new Promise((r) => setTimeout(r, 20000));
        await syncState(webClient);

        await webClient.fetch_and_cache_account_auth_by_pub_key(
          senderAccountId.to_string()
        );
        let consumeTransactionResult = await createNewConsumeTransaction(
          webClient,
          senderAccountId.to_string(),
          mintTransactionResult.created_note_ids
        );
        await new Promise((r) => setTimeout(r, 20000));
        await syncState(webClient);

        await webClient.fetch_and_cache_account_auth_by_pub_key(
          senderAccountId.to_string()
        );
        let sendTransactionResult = await createNewSendTransaction(
          webClient,
          senderAccountId.to_string(),
          targetAccountId.to_string(),
          faucetId.to_string(),
          "Private",
          "500",
          null
        );
        await new Promise((r) => setTimeout(r, 20000));
        await syncState(webClient);

        await webClient.fetch_and_cache_account_auth_by_pub_key(
          targetAccountId.to_string()
        );
        let consumeSendTransactionResult = await createNewConsumeTransaction(
          webClient,
          targetAccountId.to_string(),
          sendTransactionResult.created_note_ids
        );
        await new Promise((r) => setTimeout(r, 20000));
        await syncState(webClient);

        console.log("testNewSendTransaction finished");
      }

      // Done
      async function testNewSendTransactionWithRecallHeight() {
        console.log("testNewSendTransactionWithRecallHeight started");

        let webClient = await createMidenWebClient();
        let senderAccountId = await createNewWallet(webClient, "Private", true);
        let targetAccountId = await createNewWallet(webClient, "Private", true);
        let faucetId = await createNewFaucet(
          webClient,
          "Private",
          false,
          "DEN",
          "10",
          "1000000"
        );
        await syncState(webClient);
        await new Promise((r) => setTimeout(r, 20000));

        await webClient.fetch_and_cache_account_auth_by_pub_key(
          faucetId.to_string()
        );
        let mintTransactionResult = await createNewMintTransaction(
          webClient,
          senderAccountId.to_string(),
          faucetId.to_string(),
          "Private",
          "1000"
        );
        await new Promise((r) => setTimeout(r, 20000));
        await syncState(webClient);

        await webClient.fetch_and_cache_account_auth_by_pub_key(
          senderAccountId.to_string()
        );
        let consumeTransactionResult = await createNewConsumeTransaction(
          webClient,
          senderAccountId.to_string(),
          mintTransactionResult.created_note_ids
        );
        await new Promise((r) => setTimeout(r, 20000));
        await syncState(webClient);

        await webClient.fetch_and_cache_account_auth_by_pub_key(
          senderAccountId.to_string()
        );
        let sendTransactionResult = await createNewSendTransaction(
          webClient,
          senderAccountId.to_string(),
          targetAccountId.to_string(),
          faucetId.to_string(),
          "Private",
          "500",
          "0"
        );
        await new Promise((r) => setTimeout(r, 20000));
        await syncState(webClient);

        await webClient.fetch_and_cache_account_auth_by_pub_key(
          senderAccountId.to_string()
        );
        let consumeSendTransactionResult = await createNewConsumeTransaction(
          webClient,
          senderAccountId.to_string(),
          sendTransactionResult.created_note_ids
        );
        await new Promise((r) => setTimeout(r, 20000));
        await syncState(webClient);

        console.log("testNewSendTransactionWithRecallHeight finished");
      }

      // Done
      async function testNewSwapTransaction() {
        console.log("testNewSwapTransaction started");
        let webClient = await createMidenWebClient();

        let walletAAccountId = await createNewWallet(
          webClient,
          "Private",
          true
        );
        let walletBAccountId = await createNewWallet(
          webClient,
          "Private",
          true
        );
        let offeredAssetFaucetId = await createNewFaucet(
          webClient,
          "Private",
          false,
          "DEN",
          "10",
          "1000000"
        );
        let requestedAssetFaucetId = await createNewFaucet(
          webClient,
          "Private",
          false,
          "GAR",
          "10",
          "1000000"
        );
        await syncState(webClient);
        await new Promise((r) => setTimeout(r, 20000));

        await webClient.fetch_and_cache_account_auth_by_pub_key(
          offeredAssetFaucetId.to_string()
        );
        let walletAMintTransactionResult = await createNewMintTransaction(
          webClient,
          walletAAccountId.to_string(),
          offeredAssetFaucetId.to_string(),
          "Public",
          "1000"
        );
        await new Promise((r) => setTimeout(r, 20000));
        await syncState(webClient);

        await webClient.fetch_and_cache_account_auth_by_pub_key(
          requestedAssetFaucetId.to_string()
        );
        let walletBMintTransactionResult = await createNewMintTransaction(
          webClient,
          walletBAccountId.to_string(),
          requestedAssetFaucetId.to_string(),
          "Public",
          "1000"
        );
        await new Promise((r) => setTimeout(r, 20000));
        await syncState(webClient);

        await webClient.fetch_and_cache_account_auth_by_pub_key(
          walletAAccountId.to_string()
        );
        let walletAConsumeTransactionResult = await createNewConsumeTransaction(
          webClient,
          walletAAccountId.to_string(),
          walletAMintTransactionResult.created_note_ids
        );
        await new Promise((r) => setTimeout(r, 20000));
        await syncState(webClient);

        await webClient.fetch_and_cache_account_auth_by_pub_key(
          walletBAccountId.to_string()
        );
        let walletBConsumeTransactionResult = await createNewConsumeTransaction(
          webClient,
          walletBAccountId.to_string(),
          walletBMintTransactionResult.created_note_ids
        );
        await new Promise((r) => setTimeout(r, 20000));
        await syncState(webClient);

        await webClient.fetch_and_cache_account_auth_by_pub_key(
          walletAAccountId.to_string()
        );
        let swapTransactionResult = await createNewSwapTransaction(
          webClient,
          walletAAccountId.to_string(),
          offeredAssetFaucetId.to_string(),
          "100",
          requestedAssetFaucetId.to_string(),
          "900",
          "Public"
        );
        await new Promise((r) => setTimeout(r, 20000));
        await syncState(webClient);

        await addTag(webClient, swapTransactionResult.payback_note_tag);
        await new Promise((r) => setTimeout(r, 20000));
        await syncState(webClient);

        await webClient.fetch_and_cache_account_auth_by_pub_key(
          walletBAccountId.to_string()
        );
        let walletBConsumeSwapTransactionResult =
          await createNewConsumeTransaction(
            webClient,
            walletBAccountId.to_string(),
            swapTransactionResult.expected_output_note_ids // TODO CHANGE ME
          );
        await new Promise((r) => setTimeout(r, 20000));
        await syncState(webClient);

        await webClient.fetch_and_cache_account_auth_by_pub_key(
          walletAAccountId.to_string()
        );
        let walletAConsumeSwapTransactionResult =
          await createNewConsumeTransaction(
            webClient,
            walletAAccountId.to_string(),
            swapTransactionResult.expected_partial_note_ids // TODO CHANGE ME
          );
        await new Promise((r) => setTimeout(r, 20000));
        await syncState(webClient);

        console.log("testNewSwapTransaction finished");
      }

      // Done
      async function testGetTransactions() {
        console.log("testGetTransactions started");

        let webClient = await createMidenWebClient();
        let walletAccount = await createNewWallet(webClient, "Private", true);
        let faucetAccount = await createNewFaucet(
          webClient,
          "Private",
          false,
          "DEN",
          "10",
          "1000000"
        );
        await syncState(webClient);
        await new Promise((r) => setTimeout(r, 20000));

        await webClient.fetch_and_cache_account_auth_by_pub_key(
          faucetAccount.to_string()
        );

        let mintTransactionResult = await createNewMintTransaction(
          webClient,
          walletAccount.to_string(),
          faucetAccount.to_string(),
          "Private",
          "1000"
        );
        await new Promise((r) => setTimeout(r, 20000));
        await syncState(webClient);

        await webClient.fetch_and_cache_account_auth_by_pub_key(
          walletAccount.to_string()
        );

        let consumeTransactionResult = await createNewConsumeTransaction(
          webClient,
          walletAccount.to_string(),
          mintTransactionResult.created_note_ids
        );
        await new Promise((r) => setTimeout(r, 20000));
        await syncState(webClient);

        await getTransactions(webClient);

        console.log("testGetTransactions finished");
      }

      // Done
      async function testGetInputNotes() {
        console.log("testGetInputNotes started");

        let webClient = await createMidenWebClient();
        let targetAccountId = await createNewWallet(webClient, "Private", true);
        let faucetId = await createNewFaucet(
          webClient,
          "Private",
          false,
          "DEN",
          "10",
          "1000000"
        );
        await syncState(webClient);
        await new Promise((r) => setTimeout(r, 20000));

        await webClient.fetch_and_cache_account_auth_by_pub_key(
          faucetId.to_string()
        );

        let mintTransactionResult = await createNewMintTransaction(
          webClient,
          targetAccountId.to_string(),
          faucetId.to_string(),
          "Private",
          "1000"
        );
        await new Promise((r) => setTimeout(r, 20000));
        await syncState(webClient);

        await webClient.fetch_and_cache_account_auth_by_pub_key(
          targetAccountId.to_string()
        );

        let consumeTransactionResult = await createNewConsumeTransaction(
          webClient,
          targetAccountId.to_string(),
          mintTransactionResult.created_note_ids
        );
        await new Promise((r) => setTimeout(r, 20000));
        await syncState(webClient);

        await getInputNotes(webClient);

        console.log("testGetInputNotes finished");
      }

      // Done
      async function testGetInputNote() {
        console.log("testGetInputNote started");

        let webClient = await createMidenWebClient();
        let targetAccountId = await createNewWallet(webClient, "Private", true);
        let faucetId = await createNewFaucet(
          webClient,
          "Private",
          false,
          "DEN",
          "10",
          "1000000"
        );
        await syncState(webClient);
        await new Promise((r) => setTimeout(r, 20000));

        await webClient.fetch_and_cache_account_auth_by_pub_key(
          faucetId.to_string()
        );

        let mintTransactionResult = await createNewMintTransaction(
          webClient,
          targetAccountId.to_string(),
          faucetId.to_string(),
          "Private",
          "1000"
        );
        await new Promise((r) => setTimeout(r, 20000));
        await syncState(webClient);

        await webClient.fetch_and_cache_account_auth_by_pub_key(
          targetAccountId.to_string()
        );

        let consumeTransactionResult = await createNewConsumeTransaction(
          webClient,
          targetAccountId.to_string(),
          mintTransactionResult.created_note_ids
        );
        await new Promise((r) => setTimeout(r, 20000));
        await syncState(webClient);

        await getInputNote(
          webClient,
          mintTransactionResult.created_note_ids[0]
        );

        console.log("testGetInputNote finished");
      }

      // Done
      async function testGetNote() {
        console.log("testGetNote started");
        let webClient = await createMidenWebClient();

        // Create accounts and sync
        let regularAccountTemplate = createBasicMutableAccountTemplate("Local");
        let fungibleFaucetAccountTemplate = createFungibleFaucetAccountTemplate(
          "DEN",
          "10",
          "1000000",
          "Local"
        );
        let regularAccountId = await createNewAccount(
          webClient,
          regularAccountTemplate
        );
        let faucetId = await createNewAccount(
          webClient,
          fungibleFaucetAccountTemplate
        );
        await syncState(webClient);
        await new Promise((r) => setTimeout(r, 10000));

        // Create mint transaction and sync
        let transactionTemplate = createMintTransactionTemplate(
          regularAccountId,
          faucetId,
          "1000",
          "Private"
        );
        let createTransactionResult = await createTransaction(
          webClient,
          transactionTemplate
        );
        await new Promise((r) => setTimeout(r, 10000));
        await syncState(webClient);

        await getInputNote(
          webClient,
          createTransactionResult.created_note_ids[0]
        );

        console.log("testGetNote finished");
      }

      // Done
      async function testGetOutputNotes() {
        console.log("testGetOutputNotes started");

        let webClient = await createMidenWebClient();
        let targetAccountId = await createNewWallet(webClient, "Private", true);
        let faucetId = await createNewFaucet(
          webClient,
          "Private",
          false,
          "DEN",
          "10",
          "1000000"
        );
        await syncState(webClient);
        await new Promise((r) => setTimeout(r, 20000));

        await webClient.fetch_and_cache_account_auth_by_pub_key(
          faucetId.to_string()
        );

        let mintTransactionResult = await createNewMintTransaction(
          webClient,
          targetAccountId.to_string(),
          faucetId.to_string(),
          "Private",
          "1000"
        );
        await new Promise((r) => setTimeout(r, 20000));
        await syncState(webClient);

        await webClient.fetch_and_cache_account_auth_by_pub_key(
          targetAccountId.to_string()
        );

        let consumeTransactionResult = await createNewConsumeTransaction(
          webClient,
          targetAccountId.to_string(),
          mintTransactionResult.created_note_ids
        );
        await new Promise((r) => setTimeout(r, 20000));
        await syncState(webClient);

        await getOutputNotes(webClient);

        console.log("testGetOutputNotes finished");
      }

      // Done
      async function testGetOutputNote() {
        console.log("testGetOutputNote started");

        let webClient = await createMidenWebClient();
        let targetAccountId = await createNewWallet(webClient, "Private", true);
        let faucetId = await createNewFaucet(
          webClient,
          "Private",
          false,
          "DEN",
          "10",
          "1000000"
        );
        await syncState(webClient);
        await new Promise((r) => setTimeout(r, 20000));

        await webClient.fetch_and_cache_account_auth_by_pub_key(
          faucetId.to_string()
        );

        let mintTransactionResult = await createNewMintTransaction(
          webClient,
          targetAccountId.to_string(),
          faucetId.to_string(),
          "Private",
          "1000"
        );
        await new Promise((r) => setTimeout(r, 20000));
        await syncState(webClient);

        await webClient.fetch_and_cache_account_auth_by_pub_key(
          targetAccountId.to_string()
        );

        let consumeTransactionResult = await createNewConsumeTransaction(
          webClient,
          targetAccountId.to_string(),
          mintTransactionResult.created_note_ids
        );
        await new Promise((r) => setTimeout(r, 20000));
        await syncState(webClient);

        await getOutputNote(
          webClient,
          mintTransactionResult.created_note_ids[0]
        );

        console.log("testGetOutputNote finished");
      }

      // Done
      async function testExportNote() {
        console.log("testExportNote started");

        let webClient = await createMidenWebClient();
        // let senderAccountId = await createNewWallet(webClient, "Private", true);
        let faucetId = await createNewFaucet(
          webClient,
          "Private",
          false,
          "DEN",
          "10",
          "1000000"
        );
        await syncState(webClient);
        await new Promise((r) => setTimeout(r, 20000));

        await webClient.fetch_and_cache_account_auth_by_pub_key(faucetId);

        let mintTransactionResult = await createNewMintTransaction(
          webClient,
          "0x9186b96f559e852f", // Insert target account id here
          faucetId,
          "Private",
          "1000"
        );
        await new Promise((r) => setTimeout(r, 20000));
        await syncState(webClient);

        let result = await exportNote(
          webClient,
          mintTransactionResult.created_note_ids[0]
        );

        const blob = new Blob([result], { type: "application/octet-stream" });

        // Create a URL for the Blob
        const url = URL.createObjectURL(blob);

        // Create a temporary anchor element
        const a = document.createElement("a");
        a.href = url;
        a.download = "exportNoteTest.mno"; // Specify the file name

        // Append the anchor to the document
        document.body.appendChild(a);

        // Programmatically click the anchor to trigger the download
        a.click();

        // Remove the anchor from the document
        document.body.removeChild(a);

        // Revoke the object URL to free up resources
        URL.revokeObjectURL(url);

        console.log("testExportNote finished");
      }

      // Done
      async function testImportInputNote() {
        console.log("testImportInputNote started");

        let webClient = await createMidenWebClient();
        let walletAccount = await createNewWallet(webClient, "Private", true);

        function setupNoteFileInputListener(webClient, targetAccountId) {
          document
            .getElementById("noteFileInput")
            .addEventListener("change", async function (event) {
              const file = event.target.files[0];
              if (file) {
                const reader = new FileReader();
                reader.onload = async function (e) {
                  const arrayBuffer = e.target.result;
                  const byteArray = new Uint8Array(arrayBuffer);
                  console.log(byteArray); // Now you can work with the bytes

                  let result = await importInputNote(
                    webClient,
                    byteArray,
                    false
                  );
                  console.log(result); // Log the result of the import process

                  await webClient.fetch_and_cache_account_auth_by_pub_key(
                    targetAccountId
                  );

                  let consumeTransactionResult =
                    await createNewConsumeTransaction(
                      webClient,
                      "0x98f63aaa54c58c14",
                      // targetAccountId,
                      [result]
                    );
                  await new Promise((r) => setTimeout(r, 20000));
                  await syncState(webClient);

                  console.log("testImportInputNote finished");
                };
                reader.readAsArrayBuffer(file);
              }
            });
        }

        setupNoteFileInputListener(webClient, walletAccount);
      }

      // This test is modeled after this test in the Miden codebase:
      // https://github.com/0xPolygonMiden/miden-client/blob/2f8707faeded3725a1d732310f616e3e2170a50d/tests/integration/custom_transactions_tests.rs#L54
      async function testCustomTransaction() {
        console.log("testCustomTransaction started");
        // Creating Wallet and Faucet
        let webClient = await createMidenWebClient();
        let walletId = await createNewWallet(webClient, "Private", false);
        let faucetId = await createNewFaucet(
          webClient,
          "Private",
          false,
          "DEN",
          "10",
          "1000000"
        );
        await syncState(webClient);

        // Creating Custom Note which needs the following:
        // - Note Assets
        // - Note Metadata
        // - Note Recipient

        // Creating NOTE_ARGS
        let felt1 = new Felt(BigInt(9));
        let felt2 = new Felt(BigInt(12));
        let felt3 = new Felt(BigInt(18));
        let felt4 = new Felt(BigInt(3));
        let felt5 = new Felt(BigInt(3));
        let felt6 = new Felt(BigInt(18));
        let felt7 = new Felt(BigInt(12));
        let felt8 = new Felt(BigInt(9));
        let noteArgs = [felt1, felt2, felt3, felt4, felt5, felt6, felt7, felt8];
        let feltArray = new FeltArray();
        noteArgs.forEach((felt) => feltArray.append(felt));

        let noteAssets = new NoteAssets([
          new FungibleAsset(faucetId, BigInt(10)),
        ]);

        let noteMetadata = new NoteMetadata(
          faucetId,
          NoteType.private(),
          NoteTag.from_account_id(walletId, NoteExecutionMode.new_local()),
          NoteExecutionHint.none(),
          null
        );

        let expectedNoteArgs = noteArgs.map((felt) => felt.as_int());
        let memAddress = "1000";
        let memAddress2 = "1004";
        let expectedNoteArg1 = expectedNoteArgs.slice(0, 4).join(".");
        let expectedNoteArg2 = expectedNoteArgs.slice(4, 8).join(".");
        let note_script = `
                # Custom P2ID note script
                #
                # This note script asserts that the note args are exactly the same as passed 
                # (currently defined as {expected_note_arg_1} and {expected_note_arg_2}).
                # Since the args are too big to fit in a single note arg, we provide them via advice inputs and 
                # address them via their commitment (noted as NOTE_ARG)
                # This note script is based off of the P2ID note script because notes currently need to have 
                # assets, otherwise it could have been boiled down to the assert. 

                use.miden::account
                use.miden::note
                use.miden::contracts::wallets::basic->wallet
                use.std::mem


                proc.add_note_assets_to_account
                    push.0 exec.note::get_assets
                    # => [num_of_assets, 0 = ptr, ...]

                    # compute the pointer at which we should stop iterating
                    mul.4 dup.1 add
                    # => [end_ptr, ptr, ...]

                    # pad the stack and move the pointer to the top
                    padw movup.5
                    # => [ptr, 0, 0, 0, 0, end_ptr, ...]

                    # compute the loop latch
                    dup dup.6 neq
                    # => [latch, ptr, 0, 0, 0, 0, end_ptr, ...]

                    while.true
                        # => [ptr, 0, 0, 0, 0, end_ptr, ...]

                        # save the pointer so that we can use it later
                        dup movdn.5
                        # => [ptr, 0, 0, 0, 0, ptr, end_ptr, ...]

                        # load the asset
                        mem_loadw
                        # => [ASSET, ptr, end_ptr, ...]

                        # pad the stack before call
                        padw swapw padw padw swapdw
                        # => [ASSET, pad(12), ptr, end_ptr, ...]

                        # add asset to the account
                        call.wallet::receive_asset
                        # => [pad(16), ptr, end_ptr, ...]

                        # clean the stack after call
                        dropw dropw dropw
                        # => [0, 0, 0, 0, ptr, end_ptr, ...]

                        # increment the pointer and compare it to the end_ptr
                        movup.4 add.4 dup dup.6 neq
                        # => [latch, ptr+4, ASSET, end_ptr, ...]
                    end

                    # clear the stack
                    drop dropw drop
                end

                begin
                    # push data from the advice map into the advice stack
                    adv.push_mapval
                    # => [NOTE_ARG] 

                    # memory address where to write the data
                    push.${memAddress}
                    # => [target_mem_addr, NOTE_ARG_COMMITMENT]
                    # number of words
                    push.2
                    # => [number_of_words, target_mem_addr, NOTE_ARG_COMMITMENT]
                    exec.mem::pipe_preimage_to_memory
                    # => [target_mem_addr']
                    dropw
                    # => []
                    
                    # read first word
                    push.${memAddress}
                    # => [data_mem_address]
                    mem_loadw
                    # => [NOTE_ARG_1]
                    
                    push.${expectedNoteArg1} assert_eqw.err=101
                    # => []

                    # read second word
                    push.${memAddress2}
                    # => [data_mem_address_2]
                    mem_loadw
                    # => [NOTE_ARG_2]

                    push.${expectedNoteArg2} assert_eqw.err=102
                    # => []

                    # store the note inputs to memory starting at address 0
                    push.0 exec.note::get_inputs
                    # => [num_inputs, inputs_ptr]

                    # make sure the number of inputs is 1
                    eq.2 assert.err=103
                    # => [inputs_ptr]

                    # read the target account id from the note inputs
                    mem_load
                    # => [target_account_id_prefix]

                    exec.account::get_id swap drop
                    # => [account_id_prefix, target_account_id_prefix, ...]

                    # ensure account_id = target_account_id, fails otherwise
                    assert_eq.err=104
                    # => [...]

                    exec.add_note_assets_to_account
                    # => [...]
                end
            `;

        let compiledNoteScript =
          await webClient.compile_note_script(note_script);
        let noteInputs = new NoteInputs(new FeltArray([walletId.to_felt()]));

        let noteRecipient = new NoteRecipient(compiledNoteScript, noteInputs);

        let note = new Note(noteAssets, noteMetadata, noteRecipient);

        // Creating First Custom Transaction Request to Mint the Custom Note
        let transaction_request = new TransactionRequestBuilder()
          .with_own_output_notes(new OutputNotesArray([OutputNote.full(note)]))
          .build();

        // Execute and Submit Transaction
        await webClient.fetch_and_cache_account_auth_by_pub_key(
          faucetId.to_string()
        );
        let transaction_result = await webClient.new_transaction(
          faucetId,
          transaction_request
        );
        await webClient.submit_transaction(transaction_result);
        await new Promise((r) => setTimeout(r, 10000));
        await webClient.sync_state();

        // Just like in the miden test, you can modify this script to get the execution to fail
        // by modifying the assert
        let tx_script = `
                use.miden::contracts::auth::basic->auth_tx
                use.miden::kernels::tx::prologue
                use.miden::kernels::tx::memory

                begin
                    push.0 push.0
                    # => [0, 0]
                    assert_eq

                    call.auth_tx::auth_tx_rpo_falcon512
                end
            `;

        // Creating Second Custom Transaction Request to Consume Custom Note
        // with Invalid/Valid Transaction Script
        let account_auth = await webClient.get_account_auth(walletId);
        let public_key = account_auth.get_rpo_falcon_512_public_key_as_word();
        let secret_key = account_auth.get_rpo_falcon_512_secret_key_as_felts();
        let transcription_script_input_pair_array =
          new TransactionScriptInputPairArray([
            new TransactionScriptInputPair(public_key, secret_key),
          ]);
        let transaction_script = await webClient.compile_tx_script(
          tx_script,
          transcription_script_input_pair_array
        );
        let note_id = note.id();
        let note_args_commitment = Rpo256.hash_elements(feltArray); // gets consumed by NoteIdAndArgs
        let note_id_and_args = new NoteIdAndArgs(
          note_id,
          note_args_commitment.to_word()
        );
        let note_id_and_args_array = new NoteIdAndArgsArray([note_id_and_args]);
        let advice_map = new AdviceMap();
        let note_args_commitment_2 = Rpo256.hash_elements(feltArray);
        advice_map.insert(note_args_commitment_2, feltArray);

        let transaction_request_2 = new TransactionRequest()
          .with_authenticated_input_notes(note_id_and_args_array)
          .with_custom_script(transaction_script)
          .extend_advice_map(advice_map);

        // Execute and Submit Transaction
        await webClient.fetch_and_cache_account_auth_by_pub_key(
          walletId.to_string()
        );
        let transaction_result_2 = await webClient.new_transaction(
          walletId,
          transaction_request_2
        );
        await webClient.submit_transaction(transaction_result_2);
        await new Promise((r) => setTimeout(r, 10000));
        await webClient.sync_state();

        console.log("testCustomTransaction finished");
      }

      await testCreateNewWallet();
      // await testCreateNewFaucet();
      // await testGetAccounts();
      // await testGetAccount();
      // await testNewMintTransaction();
      // await testNewConsumeTransaction();
      // await testNewSendTransaction();
      // await testNewSendTransactionWithRecallHeight();
      // await testNewSwapTransaction();
      // await testGetTransactions();
      // await testGetInputNotes();
      // await testGetInputNote();
      // await testGetOutputNotes();
      // await testGetOutputNote();
      // await testExportNote();
      // await testImportInputNote();
      // await testCustomTransaction();
    </script>
  </body>
</html>