mirror of
https://github.com/uqbar-dao/nectar.git
synced 2024-11-22 03:04:35 +03:00
Merge branch 'develop' into bp/app-api
This commit is contained in:
commit
2d5ad486fe
30
Cargo.lock
generated
30
Cargo.lock
generated
@ -1294,12 +1294,13 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "cc"
|
||||
version = "1.1.10"
|
||||
version = "1.1.11"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e9e8aabfac534be767c909e0690571677d49f41bd8465ae876fe043d52ba5292"
|
||||
checksum = "5fb8dd288a69fc53a1996d7ecfbf4a20d59065bff137ce7e56bbd620de191189"
|
||||
dependencies = [
|
||||
"jobserver",
|
||||
"libc",
|
||||
"shlex",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -2135,10 +2136,7 @@ dependencies = [
|
||||
name = "echo"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"kinode_process_lib 0.9.0",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"wit-bindgen",
|
||||
]
|
||||
|
||||
@ -2706,6 +2704,14 @@ version = "0.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea"
|
||||
|
||||
[[package]]
|
||||
name = "help"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"kinode_process_lib 0.9.0",
|
||||
"wit-bindgen",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "hermit-abi"
|
||||
version = "0.3.9"
|
||||
@ -3057,9 +3063,9 @@ checksum = "ce23b50ad8242c51a442f3ff322d56b02f08852c77e4c0b4d3fd684abc89c683"
|
||||
|
||||
[[package]]
|
||||
name = "indexmap"
|
||||
version = "2.3.0"
|
||||
version = "2.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "de3fc2e30ba82dd1b3911c8de1ffc143c74a914a14e99514d7637e3099df5ea0"
|
||||
checksum = "93ead53efc7ea8ed3cfb0c79fc8023fbb782a5432b52830b6518941cebe6505c"
|
||||
dependencies = [
|
||||
"equivalent",
|
||||
"hashbrown 0.14.5",
|
||||
@ -3364,7 +3370,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "kinode_process_lib"
|
||||
version = "0.9.0"
|
||||
source = "git+https://github.com/kinode-dao/process_lib?branch=develop#4eccb97694578276d0bfc7fadb5cc6ce3ce91991"
|
||||
source = "git+https://github.com/kinode-dao/process_lib?branch=develop#5c1d8ed36cf10688808c09357ef0e43225396097"
|
||||
dependencies = [
|
||||
"alloy",
|
||||
"alloy-primitives",
|
||||
@ -5877,15 +5883,15 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "tower-layer"
|
||||
version = "0.3.2"
|
||||
version = "0.3.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c20c8dbed6283a09604c3e69b4b7eeb54e298b8a600d4d5ecb5ad39de609f1d0"
|
||||
checksum = "121c2a6cda46980bb0fcd1647ffaf6cd3fc79a013de288782836f6df9c48780e"
|
||||
|
||||
[[package]]
|
||||
name = "tower-service"
|
||||
version = "0.3.2"
|
||||
version = "0.3.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b6bc1c9ce2b5135ac7f93c72918fc37feb872bdc6a5533a8b85eb4b86bfdae52"
|
||||
checksum = "8df9b6e13f2d32c91b9bd719c00d1958837bc7dec474d94952798cc8e69eeec3"
|
||||
|
||||
[[package]]
|
||||
name = "tracing"
|
||||
|
@ -22,7 +22,9 @@ members = [
|
||||
"kinode/packages/kns_indexer/kns_indexer", "kinode/packages/kns_indexer/get_block", "kinode/packages/kns_indexer/state",
|
||||
"kinode/packages/settings/settings",
|
||||
"kinode/packages/terminal/terminal",
|
||||
"kinode/packages/terminal/alias", "kinode/packages/terminal/cat", "kinode/packages/terminal/echo", "kinode/packages/terminal/hi", "kinode/packages/terminal/kfetch", "kinode/packages/terminal/kill", "kinode/packages/terminal/m", "kinode/packages/terminal/top",
|
||||
"kinode/packages/terminal/alias", "kinode/packages/terminal/cat", "kinode/packages/terminal/echo",
|
||||
"kinode/packages/terminal/help", "kinode/packages/terminal/hi", "kinode/packages/terminal/kfetch",
|
||||
"kinode/packages/terminal/kill", "kinode/packages/terminal/m", "kinode/packages/terminal/top",
|
||||
"kinode/packages/terminal/net_diagnostics", "kinode/packages/terminal/peer", "kinode/packages/terminal/peers",
|
||||
"kinode/packages/tester/tester",
|
||||
]
|
||||
|
@ -38,6 +38,10 @@ rustup target add wasm32-wasi
|
||||
rustup target add wasm32-wasi --toolchain nightly
|
||||
cargo install cargo-wasi
|
||||
|
||||
# Install NPM so we can build frontends for "distro" packages.
|
||||
# https://docs.npmjs.com/downloading-and-installing-node-js-and-npm
|
||||
# If you want to skip this step, run cargo build with the environment variable SKIP_BUILD_FRONTEND=true
|
||||
|
||||
# Build the runtime, along with a number of "distro" Wasm modules.
|
||||
# The compiled binary will be at `kinode/target/debug/kinode`
|
||||
# OPTIONAL: --release flag (slower build; faster runtime; binary at `kinode/target/release/kinode`)
|
||||
@ -139,8 +143,9 @@ A list of the terminal scripts included in this distro:
|
||||
- Example: `cat /terminal:sys/pkg/scripts.json`
|
||||
- `echo <text>`: print text to the terminal.
|
||||
- Example: `echo foo`
|
||||
- `help <command>`: print the help message for a command. Leave the command blank to print the help message for all commands.
|
||||
- `hi <name> <string>`: send a text message to another node's command line.
|
||||
- Example: `hi ben.os hello world`
|
||||
- Example: `hi mothu.kino hello world`
|
||||
- `kfetch`: print system information a la neofetch. No arguments.
|
||||
- `kill <process-id>`: terminate a running process. This will bypass any restart behavior–use judiciously.
|
||||
- Example: `kill chess:chess:sys`
|
||||
|
@ -20,7 +20,7 @@ input,
|
||||
button,
|
||||
textarea,
|
||||
select {
|
||||
font: inherit;
|
||||
font-family: var(--font-family-main);
|
||||
}
|
||||
|
||||
/* Variables */
|
||||
@ -38,6 +38,8 @@ select {
|
||||
--maroon: #4f0000;
|
||||
--gray: #657b83;
|
||||
--tasteful-dark: #1f1f1f;
|
||||
|
||||
--font-family-main: 'Kode Mono', monospace;
|
||||
}
|
||||
|
||||
/* Typography */
|
||||
@ -49,10 +51,17 @@ h5,
|
||||
h6,
|
||||
p,
|
||||
label,
|
||||
li {
|
||||
li,
|
||||
span {
|
||||
font-family: var(--font-family-main);
|
||||
color: light-dark(var(--off-black), var(--off-white));
|
||||
}
|
||||
|
||||
p,
|
||||
li {
|
||||
font-size: 0.8em;
|
||||
}
|
||||
|
||||
h1 {
|
||||
font-size: 2em;
|
||||
}
|
||||
@ -78,6 +87,7 @@ h6 {
|
||||
}
|
||||
|
||||
a {
|
||||
font-family: var(--font-family-main);
|
||||
color: light-dark(var(--blue), var(--orange));
|
||||
text-decoration: none;
|
||||
}
|
||||
@ -89,7 +99,6 @@ a:hover {
|
||||
|
||||
/* Layout */
|
||||
body {
|
||||
font-family: var(--font-family-main, sans-serif);
|
||||
line-height: 1.6;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
background-color: light-dark(var(--tan), var(--tasteful-dark));
|
||||
@ -162,7 +171,7 @@ button:disabled {
|
||||
}
|
||||
|
||||
button.secondary {
|
||||
background-color: white;
|
||||
background-color: light-dark(var(--off-white), var(--off-black));
|
||||
color: var(--orange);
|
||||
border: 2px solid var(--orange);
|
||||
}
|
||||
|
@ -1,5 +1,4 @@
|
||||
use std::{
|
||||
collections::HashSet,
|
||||
fs::{self, File},
|
||||
io::{BufReader, Cursor, Read, Write},
|
||||
path::{Path, PathBuf},
|
||||
@ -9,6 +8,13 @@ use flate2::read::GzDecoder;
|
||||
use tar::Archive;
|
||||
use zip::write::FileOptions;
|
||||
|
||||
macro_rules! p {
|
||||
($($tokens: tt)*) => {
|
||||
println!("cargo:warning={}", format!($($tokens)*))
|
||||
}
|
||||
}
|
||||
|
||||
/// get cargo features to compile packages with
|
||||
fn get_features() -> String {
|
||||
let mut features = "".to_string();
|
||||
for (key, _) in std::env::vars() {
|
||||
@ -23,25 +29,30 @@ fn get_features() -> String {
|
||||
features
|
||||
}
|
||||
|
||||
fn output_reruns(dir: &Path, rerun_files: &HashSet<String>) {
|
||||
/// print `cargo:rerun-if-changed=PATH` for each path of interest
|
||||
fn output_reruns(dir: &Path) {
|
||||
// Check files individually
|
||||
if let Ok(entries) = fs::read_dir(dir) {
|
||||
for entry in entries.filter_map(|e| e.ok()) {
|
||||
let path = entry.path();
|
||||
if let Some(filename) = path.file_name().and_then(|n| n.to_str()) {
|
||||
// Check if the current file is in our list of interesting files
|
||||
if filename == "ui" {
|
||||
continue;
|
||||
}
|
||||
if rerun_files.contains(filename) {
|
||||
// If so, print a `cargo:rerun-if-changed=PATH` line for it
|
||||
println!("cargo::rerun-if-changed={}", path.display());
|
||||
continue;
|
||||
}
|
||||
}
|
||||
if path.is_dir() {
|
||||
// If the entry is a directory not in rerun_files, recursively walk it
|
||||
output_reruns(&path, rerun_files);
|
||||
if let Some(dirname) = path.file_name().and_then(|n| n.to_str()) {
|
||||
if dirname == "ui" || dirname == "target" {
|
||||
// do not prompt a rerun if only UI/build files have changed
|
||||
continue;
|
||||
}
|
||||
// If the entry is a directory not in rerun_files, recursively walk it
|
||||
output_reruns(&path);
|
||||
}
|
||||
} else {
|
||||
if let Some(filename) = path.file_name().and_then(|n| n.to_str()) {
|
||||
if filename.ends_with(".zip") || filename.ends_with(".wasm") {
|
||||
// do not prompt a rerun for compiled outputs
|
||||
continue;
|
||||
}
|
||||
// any other changed file within a package subdir prompts a rerun
|
||||
println!("cargo::rerun-if-changed={}", path.display());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -64,7 +75,9 @@ fn untar_gz_file(path: &Path, dest: &Path) -> std::io::Result<()> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// fetch .tar.gz of kinode book for docs app
|
||||
fn get_kinode_book(packages_dir: &Path) -> anyhow::Result<()> {
|
||||
p!("fetching kinode book .tar.gz");
|
||||
let rt = tokio::runtime::Runtime::new().unwrap();
|
||||
rt.block_on(async {
|
||||
let releases = kit::boot_fake_node::fetch_releases("kinode-dao", "kinode-book")
|
||||
@ -153,7 +166,7 @@ fn build_and_zip_package(
|
||||
|
||||
fn main() -> anyhow::Result<()> {
|
||||
if std::env::var("SKIP_BUILD_SCRIPT").is_ok() {
|
||||
println!("Skipping build script");
|
||||
p!("skipping build script");
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
@ -162,7 +175,7 @@ fn main() -> anyhow::Result<()> {
|
||||
let packages_dir = pwd.join("packages");
|
||||
|
||||
if std::env::var("SKIP_BUILD_FRONTEND").is_ok() {
|
||||
println!("Skipping build frontend");
|
||||
p!("skipping frontend builds");
|
||||
} else {
|
||||
// build core frontends
|
||||
let core_frontends = vec![
|
||||
@ -186,12 +199,7 @@ fn main() -> anyhow::Result<()> {
|
||||
|
||||
get_kinode_book(&packages_dir)?;
|
||||
|
||||
let rerun_files: HashSet<String> = HashSet::from([
|
||||
"Cargo.lock".to_string(),
|
||||
"Cargo.toml".to_string(),
|
||||
"src".to_string(),
|
||||
]);
|
||||
output_reruns(&parent_dir, &rerun_files);
|
||||
output_reruns(&packages_dir);
|
||||
|
||||
let features = get_features();
|
||||
|
||||
@ -201,14 +209,14 @@ fn main() -> anyhow::Result<()> {
|
||||
Ok(e) => e.path(),
|
||||
Err(_) => return None,
|
||||
};
|
||||
let parent_pkg_path = entry_path.join("pkg");
|
||||
if !parent_pkg_path.exists() {
|
||||
let child_pkg_path = entry_path.join("pkg");
|
||||
if !child_pkg_path.exists() {
|
||||
// don't run on, e.g., `.DS_Store`
|
||||
return None;
|
||||
}
|
||||
Some(build_and_zip_package(
|
||||
entry_path.clone(),
|
||||
parent_pkg_path.to_str().unwrap(),
|
||||
child_pkg_path.to_str().unwrap(),
|
||||
&features,
|
||||
))
|
||||
})
|
||||
|
@ -1,9 +1,9 @@
|
||||
import { multicallAbi, kinomapAbi, mechAbi, KINOMAP, MULTICALL, KINO_ACCOUNT_IMPL } from "./";
|
||||
import { multicallAbi, kimapAbi, mechAbi, KIMAP, MULTICALL, KINO_ACCOUNT_IMPL } from "./";
|
||||
import { encodeFunctionData, encodePacked, stringToHex } from "viem";
|
||||
|
||||
export function encodeMulticalls(metadataUri: string, metadataHash: string) {
|
||||
const metadataHashCall = encodeFunctionData({
|
||||
abi: kinomapAbi,
|
||||
abi: kimapAbi,
|
||||
functionName: 'note',
|
||||
args: [
|
||||
encodePacked(["bytes"], [stringToHex("~metadata-hash")]),
|
||||
@ -12,7 +12,7 @@ export function encodeMulticalls(metadataUri: string, metadataHash: string) {
|
||||
})
|
||||
|
||||
const metadataUriCall = encodeFunctionData({
|
||||
abi: kinomapAbi,
|
||||
abi: kimapAbi,
|
||||
functionName: 'note',
|
||||
args: [
|
||||
encodePacked(["bytes"], [stringToHex("~metadata-uri")]),
|
||||
@ -21,8 +21,8 @@ export function encodeMulticalls(metadataUri: string, metadataHash: string) {
|
||||
})
|
||||
|
||||
const calls = [
|
||||
{ target: KINOMAP, callData: metadataHashCall },
|
||||
{ target: KINOMAP, callData: metadataUriCall },
|
||||
{ target: KIMAP, callData: metadataHashCall },
|
||||
{ target: KIMAP, callData: metadataUriCall },
|
||||
];
|
||||
|
||||
const multicall = encodeFunctionData({
|
||||
@ -46,7 +46,7 @@ export function encodeIntoMintCall(multicalls: `0x${string}`, our_address: `0x${
|
||||
});
|
||||
|
||||
const mintCall = encodeFunctionData({
|
||||
abi: kinomapAbi,
|
||||
abi: kimapAbi,
|
||||
functionName: 'mint',
|
||||
args: [
|
||||
our_address,
|
||||
|
@ -2,7 +2,7 @@ import { parseAbi } from "viem";
|
||||
|
||||
export { encodeMulticalls, encodeIntoMintCall } from "./helpers";
|
||||
|
||||
export const KINOMAP: `0x${string}` = "0xcA92476B2483aBD5D82AEBF0b56701Bb2e9be658";
|
||||
export const KIMAP: `0x${string}` = "0xAfA2e57D3cBA08169b416457C14eBA2D6021c4b5";
|
||||
export const MULTICALL: `0x${string}` = "0xcA11bde05977b3631167028862bE2a173976CA11";
|
||||
export const KINO_ACCOUNT_IMPL: `0x${string}` = "0x38766C70a4FB2f23137D9251a1aA12b1143fC716";
|
||||
|
||||
@ -12,7 +12,7 @@ export const multicallAbi = parseAbi([
|
||||
`struct Call { address target; bytes callData; }`,
|
||||
]);
|
||||
|
||||
export const kinomapAbi = parseAbi([
|
||||
export const kimapAbi = parseAbi([
|
||||
"function mint(address, bytes calldata, bytes calldata, bytes calldata, address) external returns (address tba)",
|
||||
"function note(bytes calldata,bytes calldata) external returns (bytes32)",
|
||||
"function get(bytes32 node) external view returns (address tokenBoundAccount, address tokenOwner, bytes memory note)",
|
||||
|
@ -3,7 +3,7 @@ import { Link, useLocation } from "react-router-dom";
|
||||
import { useAccount, useWriteContract, useWaitForTransactionReceipt, usePublicClient } from 'wagmi'
|
||||
import { ConnectButton, useConnectModal } from '@rainbow-me/rainbowkit';
|
||||
import { keccak256, toBytes } from 'viem';
|
||||
import { mechAbi, KINOMAP, encodeIntoMintCall, encodeMulticalls, kinomapAbi, MULTICALL } from "../abis";
|
||||
import { mechAbi, KIMAP, encodeIntoMintCall, encodeMulticalls, kimapAbi, MULTICALL } from "../abis";
|
||||
import { kinohash } from '../utils/kinohash';
|
||||
import useAppsStore from "../store";
|
||||
|
||||
@ -60,8 +60,8 @@ export default function PublishPage() {
|
||||
try {
|
||||
// Check if the package already exists and get its TBA
|
||||
let data = await publicClient.readContract({
|
||||
abi: kinomapAbi,
|
||||
address: KINOMAP,
|
||||
abi: kimapAbi,
|
||||
address: KIMAP,
|
||||
functionName: 'get',
|
||||
args: [kinohash(`${packageName}.${publisherId}`)]
|
||||
});
|
||||
@ -73,8 +73,8 @@ export default function PublishPage() {
|
||||
// If the package doesn't exist, check for the publisher's TBA
|
||||
if (!currentTBA) {
|
||||
data = await publicClient.readContract({
|
||||
abi: kinomapAbi,
|
||||
address: KINOMAP,
|
||||
abi: kimapAbi,
|
||||
address: KIMAP,
|
||||
functionName: 'get',
|
||||
args: [kinohash(publisherId)]
|
||||
});
|
||||
@ -97,10 +97,10 @@ export default function PublishPage() {
|
||||
|
||||
writeContract({
|
||||
abi: mechAbi,
|
||||
address: currentTBA || KINOMAP,
|
||||
address: currentTBA || KIMAP,
|
||||
functionName: 'execute',
|
||||
args: [
|
||||
isUpdate ? MULTICALL : KINOMAP,
|
||||
isUpdate ? MULTICALL : KIMAP,
|
||||
BigInt(0),
|
||||
args,
|
||||
isUpdate ? 1 : 0
|
||||
@ -130,8 +130,8 @@ export default function PublishPage() {
|
||||
}
|
||||
|
||||
const data = await publicClient.readContract({
|
||||
abi: kinomapAbi,
|
||||
address: KINOMAP,
|
||||
abi: kimapAbi,
|
||||
address: KIMAP,
|
||||
functionName: 'get',
|
||||
args: [kinohash(`${packageName}.${publisherName}`)]
|
||||
});
|
||||
@ -150,7 +150,7 @@ export default function PublishPage() {
|
||||
address: tba as `0x${string}`,
|
||||
functionName: 'execute',
|
||||
args: [
|
||||
KINOMAP,
|
||||
KIMAP,
|
||||
BigInt(0),
|
||||
multicall,
|
||||
1
|
||||
|
@ -178,7 +178,7 @@ fn init(our: Address) {
|
||||
let path = incoming.bound_path(None);
|
||||
match path {
|
||||
"/apps" => (
|
||||
server::HttpResponse::new(http::StatusCode::BAD_REQUEST),
|
||||
server::HttpResponse::new(http::StatusCode::OK),
|
||||
Some(LazyLoadBlob::new(
|
||||
Some("application/json"),
|
||||
serde_json::to_vec(
|
||||
|
@ -15,14 +15,14 @@ const AppDisplay: React.FC<AppDisplayProps> = ({ app }) => {
|
||||
onMouseEnter={() => setIsHovered(true)}
|
||||
onMouseLeave={() => setIsHovered(false)}
|
||||
className="app-display"
|
||||
title={isHovered ? (app?.label || app?.package_name) : (!app?.path ? "This app does not serve a UI" : undefined)}
|
||||
title={app?.label}
|
||||
style={!app?.path ? { pointerEvents: 'none', textDecoration: 'none !important', filter: 'grayscale(100%)' } : {}}
|
||||
>
|
||||
{app?.base64_icon
|
||||
? <img className="app-icon" src={app.base64_icon} />
|
||||
: <img className="app-icon" src='/bird-orange.svg' />
|
||||
}
|
||||
<h6>{app?.label || app?.package_name}</h6>
|
||||
{isHovered && !app?.path && <p className="no-ui">This app does not serve a UI</p>}
|
||||
{app?.path && isHovered && <button className="app-fave-button"
|
||||
onClick={(e) => {
|
||||
e.preventDefault()
|
||||
@ -35,7 +35,7 @@ const AppDisplay: React.FC<AppDisplayProps> = ({ app }) => {
|
||||
})
|
||||
}}
|
||||
>
|
||||
{app?.favorite ? '★' : '☆'}
|
||||
<span>{app?.favorite ? '★' : '☆'}</span>
|
||||
</button>}
|
||||
</a>
|
||||
}
|
||||
|
@ -1,11 +1,10 @@
|
||||
import useHomepageStore from "../store/homepageStore"
|
||||
import { Modal } from "./Modal"
|
||||
import classNames from "classnames"
|
||||
import usePersistentStore from "../store/persistentStore"
|
||||
|
||||
const WidgetsSettingsModal = () => {
|
||||
const { apps, setShowWidgetsSettings } = useHomepageStore()
|
||||
const { widgetSettings, toggleWidgetVisibility, setWidgetSize } = usePersistentStore()
|
||||
const { widgetSettings, toggleWidgetVisibility } = usePersistentStore()
|
||||
|
||||
return <Modal
|
||||
title='Widget Settings'
|
||||
@ -25,27 +24,6 @@ const WidgetsSettingsModal = () => {
|
||||
autoFocus
|
||||
/></span>
|
||||
</div>
|
||||
<div>
|
||||
<span>Widget size</span>
|
||||
<div>
|
||||
<button
|
||||
className={classNames({
|
||||
'clear': widgetSettings[app.package_name]?.size === 'large'
|
||||
})}
|
||||
onClick={() => setWidgetSize(app.package_name, 'small')}
|
||||
>
|
||||
Small
|
||||
</button>
|
||||
<button
|
||||
className={classNames({
|
||||
'clear': widgetSettings[app.package_name]?.size !== 'large'
|
||||
})}
|
||||
onClick={() => setWidgetSize(app.package_name, 'large')}
|
||||
>
|
||||
Large
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
@ -11,11 +11,12 @@ a {
|
||||
|
||||
#homepage {
|
||||
max-width: 960px;
|
||||
margin: 0 auto;
|
||||
margin: 10px auto;
|
||||
}
|
||||
|
||||
header {
|
||||
width: 100%;
|
||||
margin-bottom: 30px;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
flex-wrap: wrap;
|
||||
@ -46,7 +47,7 @@ header button {
|
||||
flex-wrap: wrap;
|
||||
gap: 10px;
|
||||
justify-content: center;
|
||||
background-color: #4f000055;
|
||||
background-color: light-dark(#4f000055, var(--tasteful-dark));
|
||||
padding: 10px;
|
||||
border-radius: 10px;
|
||||
margin: 0 auto;
|
||||
@ -80,7 +81,16 @@ header button {
|
||||
right: 0;
|
||||
padding: 2px;
|
||||
margin: 0;
|
||||
font-size: 0.7em;
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
}
|
||||
|
||||
.app-fave-button span {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
text-align: center;
|
||||
vertical-align: middle;
|
||||
font-size: 1.1em;
|
||||
}
|
||||
|
||||
.no-ui {
|
||||
@ -91,6 +101,7 @@ header button {
|
||||
|
||||
#widgets {
|
||||
width: 100%;
|
||||
margin-top: 30px;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
flex-wrap: wrap;
|
||||
@ -143,20 +154,28 @@ footer {
|
||||
position: fixed;
|
||||
bottom: 0;
|
||||
width: 90vw;
|
||||
margin: 0 auto;
|
||||
max-height: 100vh;
|
||||
max-width: 960px;
|
||||
left: 50%;
|
||||
transform: translate(-50%, 0);
|
||||
}
|
||||
|
||||
.footer-button {
|
||||
background-color: var(--blue);
|
||||
color: light-dark(var(--off-black), var(--off-white));
|
||||
border-radius: 25px 25px 0px 0px;
|
||||
}
|
||||
|
||||
#all-apps {
|
||||
border: 1px solid black;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
flex-wrap: wrap;
|
||||
gap: 10px;
|
||||
justify-content: space-evenly;
|
||||
padding: 10px;
|
||||
background-color: #4f000085;
|
||||
border-radius: 5px 5px 0px 0px;
|
||||
background-color: light-dark(var(--gray), var(--tasteful-dark));
|
||||
border-radius: 25px 25px 0px 0px;
|
||||
}
|
||||
|
||||
.app-icon {
|
||||
@ -200,4 +219,8 @@ footer {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.widget-settings div {
|
||||
padding: 20px;
|
||||
}
|
@ -1,7 +1,7 @@
|
||||
import { useEffect, useState } from 'react'
|
||||
import KinodeBird from '../components/KinodeBird'
|
||||
import useHomepageStore from '../store/homepageStore'
|
||||
import { FaChevronDown, FaChevronUp, FaScrewdriverWrench } from 'react-icons/fa6'
|
||||
import { FaChevronDown, FaChevronUp } from 'react-icons/fa6'
|
||||
import AppsDock from '../components/AppsDock'
|
||||
import AllApps from '../components/AllApps'
|
||||
import Widgets from '../components/Widgets'
|
||||
@ -59,15 +59,15 @@ function Homepage() {
|
||||
: new Date().getHours() < 18
|
||||
? 'Good afternoon'
|
||||
: 'Good evening'}, {our}</h2>
|
||||
<a href="https://github.com/kinode-dao/kinode/releases" target="_blank">[v{version}]</a>
|
||||
<button onClick={() => setShowWidgetsSettings(true)}>
|
||||
<FaScrewdriverWrench />
|
||||
</button>
|
||||
<a href="https://github.com/kinode-dao/kinode/releases" target="_blank">[kinode v{version}]</a>
|
||||
<a href="#" onClick={(e) => { e.preventDefault(); setShowWidgetsSettings(true); }}>
|
||||
[⚙]
|
||||
</a>
|
||||
</header>
|
||||
<AppsDock />
|
||||
<Widgets />
|
||||
<footer>
|
||||
<button onClick={() => setAllAppsExpanded(!allAppsExpanded)}>
|
||||
<button className="footer-button" onClick={() => setAllAppsExpanded(!allAppsExpanded)}>
|
||||
{allAppsExpanded ? <FaChevronDown /> : <FaChevronUp />}
|
||||
<span>{allAppsExpanded ? 'Collapse' : 'All apps'}</span>
|
||||
</button>
|
||||
|
@ -306,8 +306,8 @@ fn handle_log(our: &Address, state: &mut State, log: ð::Log) -> anyhow::Resul
|
||||
let child_hash = decoded.childhash.to_string();
|
||||
let name = String::from_utf8(decoded.label.to_vec())?;
|
||||
|
||||
if !kimap::valid_name(&name, false) {
|
||||
return Err(anyhow::anyhow!("skipping invalid entry"));
|
||||
if !kimap::valid_name(&name) {
|
||||
return Err(anyhow::anyhow!("skipping invalid name: {name}"));
|
||||
}
|
||||
|
||||
let full_name = match get_parent_name(&state.names, &parent_hash) {
|
||||
@ -334,6 +334,10 @@ fn handle_log(our: &Address, state: &mut State, log: ð::Log) -> anyhow::Resul
|
||||
let note = String::from_utf8(decoded.label.to_vec())?;
|
||||
let node_hash = decoded.parenthash.to_string();
|
||||
|
||||
if !kimap::valid_note(¬e) {
|
||||
return Err(anyhow::anyhow!("skipping invalid note: {note}"));
|
||||
}
|
||||
|
||||
let Some(node_name) = get_parent_name(&state.names, &node_hash) else {
|
||||
return Err(anyhow::anyhow!("parent node for note not found"));
|
||||
};
|
||||
|
@ -60,6 +60,7 @@
|
||||
|
||||
article#node-info {
|
||||
grid-area: node-info;
|
||||
word-wrap: break-word;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: space-around;
|
||||
@ -162,9 +163,7 @@
|
||||
<p id="net-key"></p>
|
||||
<p id="ip-ports"></p>
|
||||
<p id="routers"></p>
|
||||
<button>reset networking key (TODO)</button>
|
||||
<button>adjust networking info (TODO)</button>
|
||||
<button id="shutdown">shut down node</button>
|
||||
<button id="shutdown">shut down node(!)</button>
|
||||
</article>
|
||||
|
||||
<article id="pings">
|
||||
|
@ -7,10 +7,7 @@ edition = "2021"
|
||||
simulation-mode = []
|
||||
|
||||
[dependencies]
|
||||
anyhow = "1.0"
|
||||
kinode_process_lib = { git = "https://github.com/kinode-dao/process_lib", branch = "develop" }
|
||||
serde = { version = "1.0", features = ["derive"] }
|
||||
serde_json = "1.0"
|
||||
wit-bindgen = "0.24.0"
|
||||
|
||||
[lib]
|
||||
|
17
kinode/packages/terminal/help/Cargo.toml
Normal file
17
kinode/packages/terminal/help/Cargo.toml
Normal file
@ -0,0 +1,17 @@
|
||||
[package]
|
||||
name = "help"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
||||
[features]
|
||||
simulation-mode = []
|
||||
|
||||
[dependencies]
|
||||
kinode_process_lib = { git = "https://github.com/kinode-dao/process_lib", branch = "develop" }
|
||||
wit-bindgen = "0.24.0"
|
||||
|
||||
[lib]
|
||||
crate-type = ["cdylib"]
|
||||
|
||||
[package.metadata.component]
|
||||
package = "kinode:process"
|
50
kinode/packages/terminal/help/src/lib.rs
Normal file
50
kinode/packages/terminal/help/src/lib.rs
Normal file
@ -0,0 +1,50 @@
|
||||
use kinode_process_lib::{script, Address};
|
||||
|
||||
wit_bindgen::generate!({
|
||||
path: "target/wit",
|
||||
world: "process-v0",
|
||||
});
|
||||
|
||||
const HELP_MESSAGES: [[&str; 2]; 11] = [
|
||||
["alias", "\n\x1b[1malias\x1b[0m <shorthand> <process_id>: create an alias for a script.\n - Example: \x1b[1malias get_block get_block:kns_indexer:sys\x1b[0m\n - note: all of these listed commands are just default aliases for terminal scripts."],
|
||||
["cat", "\n\x1b[1mcat\x1b[0m <vfs-file-path>: print the contents of a file in the terminal.\n - Example: \x1b[1mcat /terminal:sys/pkg/scripts.json\x1b[0m"],
|
||||
["echo", "\n\x1b[1mecho\x1b[0m <text>: print text to the terminal.\n - Example: \x1b[1mecho foo\x1b[0m"],
|
||||
["hi", "\n\x1b[1mhi\x1b[0m <name> <string>: send a text message to another node's command line.\n - Example: \x1b[1mhi mothu.kino hello world\x1b[0m"],
|
||||
["kfetch", "\n\x1b[1mkfetch\x1b[0m: print system information a la neofetch. No arguments."],
|
||||
["kill", "\n\x1b[1mkill\x1b[0m <process-id>: terminate a running process. This will bypass any restart behavior–use judiciously.\n - Example: \x1b[1mkill chess:chess:sys\x1b[0m"],
|
||||
["m", "\n\x1b[1mm\x1b[0m <address> '<json>': send an inter-process message. <address> is formatted as <node>@<process_id>. <process_id> is formatted as <process_name>:<package_name>:<publisher_node>. JSON containing spaces must be wrapped in single-quotes (\x1b[1m''\x1b[0m).\n - Example: \x1b[1mm our@eth:distro:sys \"SetPublic\" -a 5\x1b[0m\n - the '-a' flag is used to expect a response with a given timeout\n - \x1b[1mour\x1b[0m will always be interpolated by the system as your node's name"],
|
||||
["net_diagnostics", "\n\x1b[1mnet_diagnostics\x1b[0m: print some useful networking diagnostic data."],
|
||||
["peer", "\n\x1b[1mpeer\x1b[0m <name>: print the peer's PKI info, if it exists."],
|
||||
["peers", "\n\x1b[1mpeers\x1b[0m: print the peers the node currently hold connections with."],
|
||||
["top", "\n\x1b[1mtop\x1b[0m <process_id>: display kernel debugging info about a process. Leave the process ID blank to display info about all processes and get the total number of running processes.\n - Example: \x1b[1mtop net:distro:sys\x1b[0m\n - Example: \x1b[1mtop\x1b[0m"],
|
||||
];
|
||||
|
||||
script!(init);
|
||||
fn init(_our: Address, args: String) -> String {
|
||||
// if args is empty, print the entire help message.
|
||||
// if args contains the name of a command, print the help message for that command.
|
||||
// otherwise, print an error message.
|
||||
if args.is_empty() {
|
||||
let mut help_message = String::from(
|
||||
"\n====================\n\
|
||||
Kinode Terminal Help\n\
|
||||
====================\n",
|
||||
);
|
||||
|
||||
for [_, message] in HELP_MESSAGES.iter() {
|
||||
help_message.push_str(message);
|
||||
help_message.push_str("\n");
|
||||
}
|
||||
|
||||
help_message.push_str(
|
||||
"For more help, look to the documentation at book.kinode.org.\n\
|
||||
============================================================\n",
|
||||
);
|
||||
|
||||
return help_message;
|
||||
} else if let Some(message) = HELP_MESSAGES.iter().find(|[cmd, _]| cmd == &args) {
|
||||
return message[1].to_string();
|
||||
} else {
|
||||
return format!("No help found for command \x1b[1m{args}\x1b[0m");
|
||||
}
|
||||
}
|
@ -33,6 +33,14 @@
|
||||
"grant_capabilities": [],
|
||||
"wit_version": 0
|
||||
},
|
||||
"help.wasm": {
|
||||
"root": false,
|
||||
"public": false,
|
||||
"request_networking": false,
|
||||
"request_capabilities": [],
|
||||
"grant_capabilities": [],
|
||||
"wit_version": 0
|
||||
},
|
||||
"hi.wasm": {
|
||||
"root": false,
|
||||
"public": false,
|
||||
|
@ -71,6 +71,10 @@ impl TerminalState {
|
||||
"echo".to_string(),
|
||||
ProcessId::new(Some("echo"), "terminal", "sys"),
|
||||
),
|
||||
(
|
||||
"help".to_string(),
|
||||
ProcessId::new(Some("help"), "terminal", "sys"),
|
||||
),
|
||||
(
|
||||
"hi".to_string(),
|
||||
ProcessId::new(Some("hi"), "terminal", "sys"),
|
||||
@ -110,14 +114,23 @@ impl TerminalState {
|
||||
|
||||
call_init!(init);
|
||||
fn init(our: Address) {
|
||||
let mut state: TerminalState = match get_typed_state(|bytes| bincode::deserialize(bytes)) {
|
||||
Some(s) => s,
|
||||
None => {
|
||||
let state = TerminalState::new(our);
|
||||
set_state(&bincode::serialize(&state).unwrap());
|
||||
state
|
||||
}
|
||||
};
|
||||
let mut state: TerminalState =
|
||||
match get_typed_state(|bytes| bincode::deserialize::<TerminalState>(bytes)) {
|
||||
Some(mut s) => {
|
||||
// **add** the pre-installed scripts to the terminal state
|
||||
// in case new ones have been added or if user has deleted aliases
|
||||
let default_state = TerminalState::new(our);
|
||||
for (alias, process) in default_state.aliases {
|
||||
s.aliases.insert(alias, process);
|
||||
}
|
||||
s
|
||||
}
|
||||
None => {
|
||||
let state = TerminalState::new(our);
|
||||
set_state(&bincode::serialize(&state).unwrap());
|
||||
state
|
||||
}
|
||||
};
|
||||
|
||||
loop {
|
||||
let message = match await_message() {
|
||||
|
@ -143,7 +143,7 @@
|
||||
oninput="document.getElementById('password-err').style.display = 'none';" value="" class="self-stretch mb-2">
|
||||
<div id="password-err" class="login-row flex mb-2" style="display: none;"> Incorrect Password </div>
|
||||
<div class="flex flex-col leading-6 self-stretch mb-2">
|
||||
<button> Login </button>
|
||||
<button id="login-button" disabled> Login </button>
|
||||
<div class="flex flex-col mt-2 text-sm leading-6" id="fake-or-not"></div>
|
||||
</div>
|
||||
</form>
|
||||
@ -156,20 +156,27 @@
|
||||
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/crypto-js/4.2.0/crypto-js.min.js"></script>
|
||||
<script>
|
||||
if ('${fake}' === 'true') {
|
||||
document.getElementById("fake-or-not").innerHTML = "Fake node -- any password will work!";
|
||||
} else {
|
||||
document.getElementById("fake-or-not").innerHTML = "Restart your node to change networking settings.";
|
||||
}
|
||||
let isInitialized = false;
|
||||
|
||||
const firstPathItem = window.location.pathname.split('/')[1];
|
||||
const expectedSecureSubdomain = generateSecureSubdomain(firstPathItem);
|
||||
const maybeSecureSubdomain = window.location.host.split('.')[0];
|
||||
const isSecureSubdomain = expectedSecureSubdomain === maybeSecureSubdomain;
|
||||
if (isSecureSubdomain) {
|
||||
document.getElementById("node-and-domain").innerText = "${node}: authenticate for secure subdomain app " + firstPathItem;
|
||||
} else {
|
||||
document.getElementById("node-and-domain").innerText = "${node} ";
|
||||
function initializeLoginForm() {
|
||||
if ('${fake}' === 'true') {
|
||||
document.getElementById("fake-or-not").innerHTML = "Fake node -- any password will work!";
|
||||
} else {
|
||||
document.getElementById("fake-or-not").innerHTML = "Restart your node to change networking settings.";
|
||||
}
|
||||
|
||||
const firstPathItem = window.location.pathname.split('/')[1];
|
||||
const expectedSecureSubdomain = generateSecureSubdomain(firstPathItem);
|
||||
const maybeSecureSubdomain = window.location.host.split('.')[0];
|
||||
const isSecureSubdomain = expectedSecureSubdomain === maybeSecureSubdomain;
|
||||
if (isSecureSubdomain) {
|
||||
document.getElementById("node-and-domain").innerText = "${node}: authenticate for secure subdomain app " + firstPathItem;
|
||||
} else {
|
||||
document.getElementById("node-and-domain").innerText = "${node} ";
|
||||
}
|
||||
|
||||
document.getElementById("login-button").disabled = false;
|
||||
isInitialized = true;
|
||||
}
|
||||
|
||||
async function login(password) {
|
||||
@ -197,7 +204,6 @@
|
||||
document.getElementById("password").value = "";
|
||||
document.getElementById("password-err").style.display = "flex";
|
||||
document.getElementById("password").focus();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
@ -213,12 +219,15 @@
|
||||
}
|
||||
|
||||
document.addEventListener("DOMContentLoaded", () => {
|
||||
initializeLoginForm();
|
||||
const form = document.getElementById("login-form");
|
||||
form.addEventListener("submit", (e) => {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
const password = document.getElementById("password").value;
|
||||
login(password);
|
||||
if (isInitialized) {
|
||||
const password = document.getElementById("password").value;
|
||||
login(password);
|
||||
}
|
||||
});
|
||||
});
|
||||
</script>
|
||||
|
@ -17,7 +17,7 @@ type DiskKey = [u8; CREDENTIAL_LEN];
|
||||
|
||||
pub const CREDENTIAL_LEN: usize = ring::digest::SHA256_OUTPUT_LEN;
|
||||
pub const ITERATIONS: u32 = 1_000_000;
|
||||
pub static PBKDF2_ALG: pbkdf2::Algorithm = pbkdf2::PBKDF2_HMAC_SHA256; // TODO maybe look into Argon2
|
||||
pub static PBKDF2_ALG: pbkdf2::Algorithm = pbkdf2::PBKDF2_HMAC_SHA256;
|
||||
|
||||
pub fn encode_keyfile(
|
||||
password_hash: String,
|
||||
|
@ -11,6 +11,7 @@
|
||||
<meta httpEquiv="X-UA-Compatible" content="IE=edge" />
|
||||
<meta name="viewport"
|
||||
content="width=device-width, initial-scale=1, minimum-scale=1, maximum-scale=1.00001, viewport-fit=cover" />
|
||||
<link href="https://fonts.googleapis.com/css2?family=Kode+Mono:wght@700&display=swap" rel="stylesheet">
|
||||
</head>
|
||||
|
||||
<body>
|
||||
|
@ -3,6 +3,7 @@ import { Navigate, BrowserRouter as Router, Route, Routes, useParams } from 'rea
|
||||
|
||||
import CommitDotOsName from "./pages/CommitDotOsName";
|
||||
import MintDotOsName from "./pages/MintDotOsName";
|
||||
import MintCustom from "./pages/MintCustom";
|
||||
import SetPassword from "./pages/SetPassword";
|
||||
import Login from './pages/Login'
|
||||
import ResetDotOsName from './pages/ResetDotOsName'
|
||||
@ -19,7 +20,7 @@ function App() {
|
||||
const [keyFileName, setKeyFileName] = useState<string>('');
|
||||
const [reset, setReset] = useState<boolean>(false);
|
||||
const [direct, setDirect] = useState<boolean>(false);
|
||||
const [knsName, setOsName] = useState<string>('');
|
||||
const [knsName, setKnsName] = useState<string>('');
|
||||
const [appSizeOnLoad, setAppSizeOnLoad] = useState<number>(0);
|
||||
const [networkingKey, setNetworkingKey] = useState<string>('');
|
||||
const [ipAddress, setIpAddress] = useState<number>(0);
|
||||
@ -50,7 +51,7 @@ function App() {
|
||||
const info: UnencryptedIdentity = await infoResponse.json()
|
||||
|
||||
if (initialVisit) {
|
||||
setOsName(info.name)
|
||||
setKnsName(info.name)
|
||||
setRouters(info.allowed_routers)
|
||||
setNavigateToLogin(true)
|
||||
setInitialVisit(false)
|
||||
@ -87,7 +88,7 @@ function App() {
|
||||
keyFileName, setKeyFileName,
|
||||
reset, setReset,
|
||||
pw, setPw,
|
||||
knsName, setOsName,
|
||||
knsName, setKnsName,
|
||||
connectOpen, openConnect, closeConnect,
|
||||
networkingKey, setNetworkingKey,
|
||||
ipAddress, setIpAddress,
|
||||
@ -114,6 +115,7 @@ function App() {
|
||||
<Route path="/reset" element={<ResetDotOsName {...props} />} />
|
||||
<Route path="/import-keyfile" element={<ImportKeyfile {...props} />} />
|
||||
<Route path="/login" element={<Login {...props} />} />
|
||||
<Route path="/custom-register" element={<MintCustom {...props} />} />
|
||||
</Routes>
|
||||
</main>
|
||||
</Router>
|
||||
|
@ -2,7 +2,7 @@
|
||||
import { NetworkingInfo } from "../lib/types";
|
||||
import { kinohash } from "../utils/kinohash";
|
||||
import { ipToBytes, portToBytes } from "../utils/kns_encoding";
|
||||
import { multicallAbi, kinomapAbi, mechAbi, KINOMAP, MULTICALL } from "./";
|
||||
import { multicallAbi, kimapAbi, mechAbi, KIMAP, MULTICALL } from "./";
|
||||
import { encodeFunctionData, encodePacked, stringToHex, bytesToHex } from "viem";
|
||||
|
||||
// Function to encode router names into keccak256 hashes
|
||||
@ -58,7 +58,7 @@ export const generateNetworkingKeys = async ({
|
||||
console.log("networking_key: ", networking_key);
|
||||
|
||||
const netkeycall = encodeFunctionData({
|
||||
abi: kinomapAbi,
|
||||
abi: kimapAbi,
|
||||
functionName: 'note',
|
||||
args: [
|
||||
encodePacked(["bytes"], [stringToHex("~net-key")]),
|
||||
@ -68,7 +68,7 @@ export const generateNetworkingKeys = async ({
|
||||
|
||||
const ws_port_call =
|
||||
encodeFunctionData({
|
||||
abi: kinomapAbi,
|
||||
abi: kimapAbi,
|
||||
functionName: 'note',
|
||||
args: [
|
||||
encodePacked(["bytes"], [stringToHex("~ws-port")]),
|
||||
@ -78,7 +78,7 @@ export const generateNetworkingKeys = async ({
|
||||
|
||||
const tcp_port_call =
|
||||
encodeFunctionData({
|
||||
abi: kinomapAbi,
|
||||
abi: kimapAbi,
|
||||
functionName: 'note',
|
||||
args: [
|
||||
encodePacked(["bytes"], [stringToHex("~tcp-port")]),
|
||||
@ -88,7 +88,7 @@ export const generateNetworkingKeys = async ({
|
||||
|
||||
const ip_address_call =
|
||||
encodeFunctionData({
|
||||
abi: kinomapAbi,
|
||||
abi: kimapAbi,
|
||||
functionName: 'note',
|
||||
args: [
|
||||
encodePacked(["bytes"], [stringToHex("~ip")]),
|
||||
@ -100,7 +100,7 @@ export const generateNetworkingKeys = async ({
|
||||
|
||||
const router_call =
|
||||
encodeFunctionData({
|
||||
abi: kinomapAbi,
|
||||
abi: kimapAbi,
|
||||
functionName: 'note',
|
||||
args: [
|
||||
encodePacked(["bytes"], [stringToHex("~routers")]),
|
||||
@ -111,13 +111,13 @@ export const generateNetworkingKeys = async ({
|
||||
});
|
||||
|
||||
const calls = direct ? [
|
||||
{ target: KINOMAP, callData: netkeycall },
|
||||
{ target: KINOMAP, callData: ws_port_call },
|
||||
{ target: KINOMAP, callData: tcp_port_call },
|
||||
{ target: KINOMAP, callData: ip_address_call },
|
||||
{ target: KIMAP, callData: netkeycall },
|
||||
{ target: KIMAP, callData: ws_port_call },
|
||||
{ target: KIMAP, callData: tcp_port_call },
|
||||
{ target: KIMAP, callData: ip_address_call },
|
||||
] : [
|
||||
{ target: KINOMAP, callData: netkeycall },
|
||||
{ target: KINOMAP, callData: router_call },
|
||||
{ target: KIMAP, callData: netkeycall },
|
||||
{ target: KIMAP, callData: router_call },
|
||||
];
|
||||
|
||||
const multicalls = encodeFunctionData({
|
||||
@ -143,7 +143,7 @@ export const generateNetworkingKeys = async ({
|
||||
|
||||
// to mint a subname of your own, you would do something like this.
|
||||
// const mintCall = encodeFunctionData({
|
||||
// abi: kinomapAbi,
|
||||
// abi: kimapAbi,
|
||||
// functionName: 'mint',
|
||||
// args: [
|
||||
// our_address,
|
||||
|
@ -3,7 +3,7 @@ import { parseAbi } from "viem";
|
||||
export { generateNetworkingKeys } from "./helpers";
|
||||
|
||||
// move to constants? // also for anvil/optimism
|
||||
export const KINOMAP: `0x${string}` = "0xcA92476B2483aBD5D82AEBF0b56701Bb2e9be658";
|
||||
export const KIMAP: `0x${string}` = "0xcA92476B2483aBD5D82AEBF0b56701Bb2e9be658";
|
||||
export const MULTICALL: `0x${string}` = "0xcA11bde05977b3631167028862bE2a173976CA11";
|
||||
export const KINO_ACCOUNT_IMPL: `0x${string}` = "0x38766C70a4FB2f23137D9251a1aA12b1143fC716";
|
||||
export const DOTOS: `0x${string}` = "0x9BD054E4c7753791FA0C138b9713319F62ed235D";
|
||||
@ -13,7 +13,7 @@ export const multicallAbi = parseAbi([
|
||||
`struct Call { address target; bytes callData; }`,
|
||||
]);
|
||||
|
||||
export const kinomapAbi = parseAbi([
|
||||
export const kimapAbi = parseAbi([
|
||||
"function mint(address, bytes calldata, bytes calldata, bytes calldata, address) external returns (address tba)",
|
||||
"function note(bytes calldata,bytes calldata) external returns (bytes32)",
|
||||
"function get(bytes32 node) external view returns (address tokenBoundAccount, address tokenOwner, bytes memory note)",
|
||||
@ -24,28 +24,11 @@ export const mechAbi = parseAbi([
|
||||
"function token() external view returns (uint256,address,uint256)"
|
||||
])
|
||||
|
||||
export const dotOsAbi = [
|
||||
{
|
||||
type: 'function',
|
||||
name: 'commit',
|
||||
stateMutability: 'nonpayable',
|
||||
inputs: [
|
||||
{ name: '_commit', type: 'bytes32' },
|
||||
],
|
||||
outputs: [],
|
||||
},
|
||||
{
|
||||
type: 'function',
|
||||
name: 'mint',
|
||||
stateMutability: 'nonpayable',
|
||||
inputs: [
|
||||
{ name: 'who', type: 'address' },
|
||||
{ name: 'name', type: 'bytes' },
|
||||
{ name: 'initialization', type: 'bytes' },
|
||||
{ name: 'erc721Data', type: 'bytes' },
|
||||
{ name: 'implementation', type: 'address' },
|
||||
{ name: 'secret', type: 'bytes32' },
|
||||
],
|
||||
outputs: [{ type: 'address' }],
|
||||
},
|
||||
] as const
|
||||
export const dotOsAbi = parseAbi([
|
||||
"function commit(bytes32 _commit) external",
|
||||
"function mint(address who, bytes calldata name, bytes calldata initialization, bytes calldata erc721Data, address implementation, bytes32 secret) external returns (address)"
|
||||
]);
|
||||
|
||||
export const customAbi = parseAbi([
|
||||
"function mint(address who, bytes calldata name, bytes calldata initialization, bytes calldata erc721Data, address implementation) external returns (address)"
|
||||
]);
|
@ -1,3 +1,5 @@
|
||||
import { Tooltip } from "./Tooltip";
|
||||
|
||||
export const DirectTooltip: React.FC = () => <Tooltip text={`A direct node publishes its own networking information on-chain: IP, port, so on. An indirect node relies on the service of routers, which are themselves direct nodes. Only register a direct node if you know what you’re doing and have a public, static IP address.`} />
|
||||
export const DirectTooltip: React.FC = () => <Tooltip text={`A direct node publishes its own networking information on-chain: IP, port, so on. An indirect node relies on the service of routers, which are themselves direct nodes. Only register a direct node if you know what you're doing and have a public, static IP address.`}>
|
||||
<span>ⓘ</span>
|
||||
</Tooltip>
|
@ -1,9 +1,9 @@
|
||||
import React, { useEffect, useRef } from "react";
|
||||
import React, { useEffect, useRef, useState } from "react";
|
||||
import isValidDomain from "is-valid-domain";
|
||||
import { toAscii } from "idna-uts46-hx";
|
||||
import { usePublicClient } from 'wagmi'
|
||||
|
||||
import { KINOMAP, kinomapAbi } from '../abis'
|
||||
import { KIMAP, kimapAbi } from '../abis'
|
||||
import { kinohash } from "../utils/kinohash";
|
||||
|
||||
export const NAME_URL = "Name must contain only valid characters (a-z, 0-9, and -)";
|
||||
@ -14,33 +14,38 @@ export const NAME_NOT_OWNER = "Name already exists and does not belong to this w
|
||||
export const NAME_NOT_REGISTERED = "Name is not registered";
|
||||
|
||||
type ClaimOsNameProps = {
|
||||
address?: `0x${string}`;
|
||||
name: string;
|
||||
setName: React.Dispatch<React.SetStateAction<string>>;
|
||||
nameValidities: string[];
|
||||
setNameValidities: React.Dispatch<React.SetStateAction<string[]>>;
|
||||
triggerNameCheck: boolean;
|
||||
setTba?: React.Dispatch<React.SetStateAction<string>>;
|
||||
isReset?: boolean;
|
||||
};
|
||||
|
||||
function EnterKnsName({
|
||||
address,
|
||||
name,
|
||||
setName,
|
||||
nameValidities,
|
||||
setNameValidities,
|
||||
triggerNameCheck,
|
||||
setTba,
|
||||
isReset = false,
|
||||
}: ClaimOsNameProps) {
|
||||
const client = usePublicClient();
|
||||
const debouncer = useRef<NodeJS.Timeout | null>(null);
|
||||
|
||||
useEffect(() => {
|
||||
if (isReset) return;
|
||||
const [isPunyfied, setIsPunyfied] = useState('');
|
||||
|
||||
useEffect(() => {
|
||||
if (debouncer.current) clearTimeout(debouncer.current);
|
||||
|
||||
debouncer.current = setTimeout(async () => {
|
||||
let index: number;
|
||||
let validities = [...nameValidities];
|
||||
let validities: string[] = [];
|
||||
setIsPunyfied('');
|
||||
|
||||
const len = [...name].length;
|
||||
index = validities.indexOf(NAME_LENGTH);
|
||||
@ -57,6 +62,8 @@ function EnterKnsName({
|
||||
if (index === -1) validities.push(NAME_INVALID_PUNY);
|
||||
}
|
||||
|
||||
if (normalized !== (name + ".os")) setIsPunyfied(normalized);
|
||||
|
||||
// only check if name is valid punycode
|
||||
if (normalized && normalized !== '.os') {
|
||||
index = validities.indexOf(NAME_URL);
|
||||
@ -66,46 +73,57 @@ function EnterKnsName({
|
||||
|
||||
index = validities.indexOf(NAME_CLAIMED);
|
||||
|
||||
// only check if name is valid and long enough
|
||||
if (validities.length === 0 || index !== -1 && normalized.length > 2) {
|
||||
try {
|
||||
const namehash = kinohash(normalized)
|
||||
// maybe separate into helper function for readability?
|
||||
// also note picking the right chain ID & address!
|
||||
const data = await client?.readContract({
|
||||
address: KINOMAP,
|
||||
abi: kinomapAbi,
|
||||
address: KIMAP,
|
||||
abi: kimapAbi,
|
||||
functionName: "get",
|
||||
args: [namehash]
|
||||
})
|
||||
|
||||
const tba = data?.[0];
|
||||
if (tba !== undefined) {
|
||||
setTba ? (setTba(tba)) : null;
|
||||
} else {
|
||||
validities.push(NAME_NOT_REGISTERED);
|
||||
}
|
||||
|
||||
const owner = data?.[1];
|
||||
const owner_is_zero = owner === "0x0000000000000000000000000000000000000000";
|
||||
|
||||
if (!owner_is_zero && index === -1) validities.push(NAME_CLAIMED);
|
||||
if (!owner_is_zero && !isReset) validities.push(NAME_CLAIMED);
|
||||
|
||||
if (!owner_is_zero && isReset && address && owner !== address) validities.push(NAME_NOT_OWNER);
|
||||
|
||||
if (isReset && owner_is_zero) validities.push(NAME_NOT_REGISTERED);
|
||||
} catch (e) {
|
||||
console.error({ e })
|
||||
if (index !== -1) validities.splice(index, 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
setNameValidities(validities);
|
||||
}, 100);
|
||||
}, 500);
|
||||
}, [name, triggerNameCheck, isReset]);
|
||||
|
||||
const noDots = (e: any) =>
|
||||
e.target.value.indexOf(".") === -1 && setName(e.target.value);
|
||||
const noDotsOrSpaces = (e: any) =>
|
||||
e.target.value.indexOf(".") === -1 && e.target.value.indexOf(" ") === -1 && setName(e.target.value);
|
||||
|
||||
return (
|
||||
<div className="enter-kns-name">
|
||||
<div className="input-wrapper">
|
||||
<input
|
||||
value={name}
|
||||
onChange={noDots}
|
||||
onChange={noDotsOrSpaces}
|
||||
type="text"
|
||||
required
|
||||
name="dot-os-name"
|
||||
placeholder="e.g. myname"
|
||||
placeholder="mynode123"
|
||||
className="kns-input"
|
||||
/>
|
||||
<span className="kns-suffix">.os</span>
|
||||
@ -113,6 +131,7 @@ function EnterKnsName({
|
||||
{nameValidities.map((x, i) => (
|
||||
<p key={i} className="error-message">{x}</p>
|
||||
))}
|
||||
{isPunyfied !== '' && <p className="puny-warning">special characters will be converted to punycode: {isPunyfied}</p>}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
@ -29,6 +29,7 @@
|
||||
}
|
||||
|
||||
.tooltip-text {
|
||||
font-size: 0.8em;
|
||||
visibility: hidden;
|
||||
width: 200px;
|
||||
background-color: #555;
|
||||
@ -127,13 +128,14 @@
|
||||
.kns-input {
|
||||
flex-grow: 1;
|
||||
padding: 0.5rem;
|
||||
font-size: 1.2em;
|
||||
border: 1px solid var(--gray);
|
||||
border-radius: 4px 0 0 4px;
|
||||
}
|
||||
|
||||
.kns-suffix {
|
||||
padding: 0.5rem;
|
||||
background-color: var(--gray);
|
||||
background-color: var(--blue);
|
||||
border: 1px solid var(--tasteful-dark);
|
||||
border-left: none;
|
||||
border-radius: 0 4px 4px 0;
|
||||
@ -148,6 +150,7 @@
|
||||
.error-message {
|
||||
color: var(--ansi-red);
|
||||
margin-top: 0.5rem;
|
||||
overflow-wrap: break-word;
|
||||
}
|
||||
|
||||
.direct-checkbox {
|
||||
@ -215,6 +218,7 @@
|
||||
|
||||
.checkbox-label {
|
||||
margin-left: 10px;
|
||||
font-size: 0.9em;
|
||||
}
|
||||
|
||||
.file-input-label {
|
||||
@ -235,4 +239,8 @@
|
||||
.file-input-label:hover .button {
|
||||
background-color: var(--dark-orange);
|
||||
color: var(--off-white);
|
||||
}
|
||||
|
||||
button.secondary {
|
||||
width: 100%;
|
||||
}
|
@ -13,7 +13,7 @@ export interface PageProps {
|
||||
direct: boolean,
|
||||
setDirect: React.Dispatch<React.SetStateAction<boolean>>,
|
||||
knsName: string,
|
||||
setOsName: React.Dispatch<React.SetStateAction<string>>,
|
||||
setKnsName: React.Dispatch<React.SetStateAction<string>>,
|
||||
key: string,
|
||||
keyFileName: string,
|
||||
setKeyFileName: React.Dispatch<React.SetStateAction<string>>,
|
||||
|
@ -17,7 +17,7 @@ interface RegisterOsNameProps extends PageProps { }
|
||||
function CommitDotOsName({
|
||||
direct,
|
||||
setDirect,
|
||||
setOsName,
|
||||
setKnsName,
|
||||
setNetworkingKey,
|
||||
setIpAddress,
|
||||
setWsPort,
|
||||
@ -51,7 +51,13 @@ function CommitDotOsName({
|
||||
|
||||
useEffect(() => setTriggerNameCheck(!triggerNameCheck), [address])
|
||||
|
||||
const enterOsNameProps = { name, setName, nameValidities, setNameValidities, triggerNameCheck }
|
||||
const enterOsNameProps = { address, name, setName, nameValidities, setNameValidities, triggerNameCheck }
|
||||
|
||||
useEffect(() => {
|
||||
if (!address) {
|
||||
openConnectModal?.();
|
||||
}
|
||||
}, [address, openConnectModal]);
|
||||
|
||||
let handleCommit = useCallback(async (e: FormEvent) => {
|
||||
e.preventDefault()
|
||||
@ -80,23 +86,23 @@ function CommitDotOsName({
|
||||
|
||||
useEffect(() => {
|
||||
if (isConfirmed) {
|
||||
setOsName(`${name}.os`);
|
||||
setKnsName(`${name}.os`);
|
||||
navigate("/mint-os-name");
|
||||
}
|
||||
}, [isConfirmed, address, name, setOsName, navigate]);
|
||||
}, [isConfirmed, address, name, setKnsName, navigate]);
|
||||
|
||||
return (
|
||||
<div className="container fade-in">
|
||||
<div className="section">
|
||||
{Boolean(address) && (
|
||||
{
|
||||
<form className="form" onSubmit={handleCommit}>
|
||||
{isPending || isConfirming ? (
|
||||
<Loader msg={isConfirming ? 'Pre-committing to chosen ID...' : 'Please confirm the transaction in your wallet'} />
|
||||
<Loader msg={isConfirming ? 'Pre-committing to chosen name...' : 'Please confirm the transaction in your wallet'} />
|
||||
) : (
|
||||
<>
|
||||
<h3 className="form-label">
|
||||
<Tooltip text="Kinodes need an onchain node identity in order to communicate with other nodes in the network.">
|
||||
Choose a name for your Kinode
|
||||
Choose a name for your node
|
||||
</Tooltip>
|
||||
</h3>
|
||||
<EnterKnsName {...enterOsNameProps} />
|
||||
@ -107,21 +113,22 @@ function CommitDotOsName({
|
||||
type="submit"
|
||||
className="button"
|
||||
>
|
||||
Register .os name
|
||||
Register name
|
||||
</button>
|
||||
<p>This will confirm availability of the name and reserve it, then on the next screen you will be prompted to mint.</p>
|
||||
<Link to="/reset" className="button secondary">
|
||||
Already have a dot-os-name?
|
||||
Already have a node?
|
||||
</Link>
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
{isError && (
|
||||
<p className="error-message">
|
||||
Error: {error?.message || 'There was an error registering your dot-os-name, please try again.'}
|
||||
Error: {error?.message || 'There was an error registering your name, please try again.'}
|
||||
</p>
|
||||
)}
|
||||
</form>
|
||||
)}
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
@ -62,7 +62,7 @@ function ImportKeyfile({
|
||||
credentials: 'include',
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify({
|
||||
keyfile: Buffer.from(localKey).toString('base64'),
|
||||
keyfile: Buffer.from(localKey).toString('utf8'),
|
||||
password_hash: hashed_password,
|
||||
}),
|
||||
});
|
||||
@ -118,13 +118,13 @@ function ImportKeyfile({
|
||||
required
|
||||
minLength={6}
|
||||
name="password"
|
||||
placeholder="Min 6 characters"
|
||||
placeholder=""
|
||||
value={pw}
|
||||
onChange={(e) => setPw(e.target.value)}
|
||||
/>
|
||||
{pwErr && <p className="error-message">{pwErr}</p>}
|
||||
{pwDebounced && !pwVet && 6 <= pw.length && (
|
||||
<p className="error-message">Password is incorrect</p>
|
||||
<p className="error-message">Password is incorrect!</p>
|
||||
)}
|
||||
</div>
|
||||
|
||||
@ -132,7 +132,7 @@ function ImportKeyfile({
|
||||
{keyErrs.map((x, i) => (
|
||||
<p key={i} className="error-message">{x}</p>
|
||||
))}
|
||||
<button type="submit" className="button">Import Keyfile</button>
|
||||
<button type="submit" className="button">Boot Node</button>
|
||||
</div>
|
||||
<p className="text-sm mt-2">
|
||||
Please note: if the original node was booted as a direct node
|
||||
|
@ -12,7 +12,7 @@ function KinodeHome({ knsName }: OsHomeProps) {
|
||||
const resetRedir = () => navigate('/reset')
|
||||
const importKeyfileRedir = () => navigate('/import-keyfile')
|
||||
const loginRedir = () => navigate('/login')
|
||||
|
||||
const customRegisterRedir = () => navigate('/custom-register')
|
||||
const previouslyBooted = Boolean(knsName)
|
||||
|
||||
useEffect(() => {
|
||||
@ -27,25 +27,28 @@ function KinodeHome({ knsName }: OsHomeProps) {
|
||||
{previouslyBooted ? (
|
||||
<div className="text-center">
|
||||
<h2 className="mb-2">Welcome back!</h2>
|
||||
<button onClick={loginRedir} className="button">Login</button>
|
||||
<button onClick={loginRedir} className="button">Log in</button>
|
||||
</div>
|
||||
) : (
|
||||
<>
|
||||
<h2 className="text-center mb-2">Welcome to Kinode</h2>
|
||||
<h4 className="text-center mb-2">New here? Register a username to get started</h4>
|
||||
<h4 className="text-center mb-2">New here? Register a name to get started</h4>
|
||||
<div className="button-group">
|
||||
<button onClick={registerRedir} className="button">
|
||||
Register Kinode Name
|
||||
Register .os Name
|
||||
</button>
|
||||
</div>
|
||||
<h4 className="text-center mt-2 mb-2">Other options</h4>
|
||||
<div className="button-group">
|
||||
<button onClick={resetRedir} className="button secondary">
|
||||
Reset Kinode Name
|
||||
</button>
|
||||
<button onClick={importKeyfileRedir} className="button secondary">
|
||||
Import Keyfile
|
||||
</button>
|
||||
<button onClick={resetRedir} className="button secondary">
|
||||
Reset Existing Name
|
||||
</button>
|
||||
<button onClick={customRegisterRedir} className="button secondary">
|
||||
Register Non-.os Name (Advanced)
|
||||
</button>
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
|
@ -3,6 +3,7 @@ import { PageProps, UnencryptedIdentity } from "../lib/types";
|
||||
import Loader from "../components/Loader";
|
||||
import { useNavigate } from "react-router-dom";
|
||||
import { sha256, toBytes } from "viem";
|
||||
import { Tooltip } from "../components/Tooltip";
|
||||
|
||||
interface LoginProps extends PageProps { }
|
||||
|
||||
@ -13,7 +14,7 @@ function Login({
|
||||
routers,
|
||||
setRouters,
|
||||
knsName,
|
||||
setOsName,
|
||||
setKnsName,
|
||||
}: LoginProps) {
|
||||
const navigate = useNavigate();
|
||||
|
||||
@ -29,7 +30,7 @@ function Login({
|
||||
res.json()
|
||||
)) as UnencryptedIdentity;
|
||||
setRouters(infoData.allowed_routers);
|
||||
setOsName(infoData.name);
|
||||
setKnsName(infoData.name);
|
||||
} catch { }
|
||||
})();
|
||||
}, []); // eslint-disable-line react-hooks/exhaustive-deps
|
||||
@ -89,8 +90,9 @@ function Login({
|
||||
>
|
||||
<div className="form-group">
|
||||
<div className="form-header">
|
||||
<h3>{knsName}</h3>
|
||||
<span>({isDirect ? "direct" : "indirect"} node)</span>
|
||||
<Tooltip text={`(${isDirect ? "direct" : "indirect"} node)`}>
|
||||
<h3>{knsName}</h3>
|
||||
</Tooltip>
|
||||
</div>
|
||||
<input
|
||||
type="password"
|
||||
@ -113,14 +115,14 @@ function Login({
|
||||
</div>
|
||||
)}
|
||||
|
||||
<button type="submit">Login</button>
|
||||
<button type="submit">Log in</button>
|
||||
|
||||
<div className="additional-options">
|
||||
<button
|
||||
className="clear"
|
||||
className="secondary"
|
||||
onClick={() => navigate('/reset')}
|
||||
>
|
||||
Reset Node & Networking Info
|
||||
Reset Password & Networking Info
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
|
154
kinode/src/register-ui/src/pages/MintCustom.tsx
Normal file
154
kinode/src/register-ui/src/pages/MintCustom.tsx
Normal file
@ -0,0 +1,154 @@
|
||||
import { useState, useEffect, FormEvent, useCallback } from "react";
|
||||
import { useNavigate } from "react-router-dom";
|
||||
import Loader from "../components/Loader";
|
||||
import { PageProps } from "../lib/types";
|
||||
|
||||
import DirectCheckbox from "../components/DirectCheckbox";
|
||||
|
||||
import { useAccount, useWaitForTransactionReceipt, useSendTransaction } from "wagmi";
|
||||
import { useConnectModal, useAddRecentTransaction } from "@rainbow-me/rainbowkit"
|
||||
import { customAbi, generateNetworkingKeys, KINO_ACCOUNT_IMPL } from "../abis";
|
||||
import { encodePacked, encodeFunctionData, stringToHex } from "viem";
|
||||
|
||||
interface MintCustomNameProps extends PageProps { }
|
||||
|
||||
function MintCustom({
|
||||
direct,
|
||||
setDirect,
|
||||
knsName,
|
||||
setKnsName,
|
||||
setNetworkingKey,
|
||||
setIpAddress,
|
||||
setWsPort,
|
||||
setTcpPort,
|
||||
setRouters,
|
||||
}: MintCustomNameProps) {
|
||||
let { address } = useAccount();
|
||||
let navigate = useNavigate();
|
||||
let { openConnectModal } = useConnectModal();
|
||||
|
||||
const { data: hash, sendTransaction, isPending, isError, error } = useSendTransaction({
|
||||
mutation: {
|
||||
onSuccess: (data) => {
|
||||
addRecentTransaction({ hash: data, description: `Mint ${knsName}` });
|
||||
}
|
||||
}
|
||||
});
|
||||
const { isLoading: isConfirming, isSuccess: isConfirmed } =
|
||||
useWaitForTransactionReceipt({
|
||||
hash,
|
||||
});
|
||||
const addRecentTransaction = useAddRecentTransaction();
|
||||
|
||||
const [triggerNameCheck, setTriggerNameCheck] = useState<boolean>(false)
|
||||
|
||||
useEffect(() => {
|
||||
document.title = "Mint"
|
||||
}, [])
|
||||
|
||||
useEffect(() => setTriggerNameCheck(!triggerNameCheck), [address])
|
||||
|
||||
useEffect(() => {
|
||||
if (!address) {
|
||||
openConnectModal?.();
|
||||
}
|
||||
}, [address, openConnectModal]);
|
||||
|
||||
let handleMint = useCallback(async (e: FormEvent) => {
|
||||
e.preventDefault()
|
||||
e.stopPropagation()
|
||||
|
||||
const formData = new FormData(e.target as HTMLFormElement)
|
||||
|
||||
if (!address) {
|
||||
openConnectModal?.()
|
||||
return
|
||||
}
|
||||
|
||||
const initCall = await generateNetworkingKeys({
|
||||
direct,
|
||||
our_address: address,
|
||||
label: knsName,
|
||||
setNetworkingKey,
|
||||
setIpAddress,
|
||||
setWsPort,
|
||||
setTcpPort,
|
||||
setRouters,
|
||||
reset: false,
|
||||
});
|
||||
|
||||
setKnsName(formData.get('full-kns-name') as string)
|
||||
|
||||
const name = formData.get('name') as string
|
||||
|
||||
console.log("full kns name", formData.get('full-kns-name'))
|
||||
console.log("name", name)
|
||||
|
||||
const data = encodeFunctionData({
|
||||
abi: customAbi,
|
||||
functionName: 'mint',
|
||||
args: [
|
||||
address,
|
||||
encodePacked(["bytes"], [stringToHex(name)]),
|
||||
initCall,
|
||||
"0x",
|
||||
KINO_ACCOUNT_IMPL,
|
||||
],
|
||||
})
|
||||
|
||||
// use data to write to contract -- do NOT use writeContract
|
||||
// writeContract will NOT generate the correct selector for some reason
|
||||
// probably THEIR bug.. no abi works
|
||||
try {
|
||||
sendTransaction({
|
||||
to: formData.get('tba') as `0x${string}`,
|
||||
data: data,
|
||||
gas: 1000000n,
|
||||
})
|
||||
} catch (error) {
|
||||
console.error('Failed to send transaction:', error)
|
||||
}
|
||||
}, [direct, address, sendTransaction, setNetworkingKey, setIpAddress, setWsPort, setTcpPort, setRouters, openConnectModal])
|
||||
|
||||
useEffect(() => {
|
||||
if (isConfirmed) {
|
||||
navigate("/set-password");
|
||||
}
|
||||
}, [isConfirmed, address, navigate]);
|
||||
|
||||
return (
|
||||
<div className="container fade-in">
|
||||
<div className="section">
|
||||
{
|
||||
<form className="form" onSubmit={handleMint}>
|
||||
{isPending || isConfirming ? (
|
||||
<Loader msg={isConfirming ? 'Minting name...' : 'Please confirm the transaction in your wallet'} />
|
||||
) : (
|
||||
<>
|
||||
<p className="form-label">
|
||||
Register a name on a different top-level zone -- this will likely fail if that zone's requirements are not met
|
||||
</p>
|
||||
<input type="text" name="name" placeholder="Enter kimap name" />
|
||||
<input type="text" name="full-kns-name" placeholder="Enter full KNS name" />
|
||||
<input type="text" name="tba" placeholder="Enter TBA to mint under" />
|
||||
<DirectCheckbox {...{ direct, setDirect }} />
|
||||
<div className="button-group">
|
||||
<button type="submit" className="button">
|
||||
Mint custom name
|
||||
</button>
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
{isError && (
|
||||
<p className="error-message">
|
||||
Error: {error?.message || 'There was an error minting your name, please try again.'}
|
||||
</p>
|
||||
)}
|
||||
</form>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default MintCustom;
|
@ -44,6 +44,12 @@ function MintDotOsName({
|
||||
|
||||
useEffect(() => setTriggerNameCheck(!triggerNameCheck), [address])
|
||||
|
||||
useEffect(() => {
|
||||
if (!address) {
|
||||
openConnectModal?.();
|
||||
}
|
||||
}, [address, openConnectModal]);
|
||||
|
||||
let handleMint = useCallback(async (e: FormEvent) => {
|
||||
e.preventDefault()
|
||||
e.stopPropagation()
|
||||
@ -107,26 +113,26 @@ function MintDotOsName({
|
||||
return (
|
||||
<div className="container fade-in">
|
||||
<div className="section">
|
||||
{Boolean(address) && (
|
||||
{
|
||||
<form className="form" onSubmit={handleMint}>
|
||||
{isPending || isConfirming ? (
|
||||
<Loader msg={isConfirming ? 'Minting .os name...' : 'Please confirm the transaction in your wallet'} />
|
||||
<Loader msg={isConfirming ? 'Minting name...' : 'Please confirm the transaction in your wallet'} />
|
||||
) : (
|
||||
<>
|
||||
<div className="button-group">
|
||||
<button type="submit" className="button">
|
||||
Mint pre-committed .os name
|
||||
Mint {knsName}
|
||||
</button>
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
{isError && (
|
||||
<p className="error-message">
|
||||
Error: {error?.message || 'There was an error minting your dot-os-name, please try again.'}
|
||||
Error: {error?.message || 'There was an error minting your name, please try again.'}
|
||||
</p>
|
||||
)}
|
||||
</form>
|
||||
)}
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
@ -2,24 +2,18 @@ import {
|
||||
FormEvent,
|
||||
useCallback,
|
||||
useEffect,
|
||||
useRef,
|
||||
useState,
|
||||
} from "react";
|
||||
import { useNavigate } from "react-router-dom";
|
||||
import { toAscii } from "idna-uts46-hx";
|
||||
import isValidDomain from "is-valid-domain";
|
||||
import Loader from "../components/Loader";
|
||||
import { PageProps } from "../lib/types";
|
||||
import { KINOMAP, MULTICALL, generateNetworkingKeys, kinomapAbi, mechAbi } from "../abis";
|
||||
import { MULTICALL, generateNetworkingKeys, mechAbi } from "../abis";
|
||||
import { Tooltip } from "../components/Tooltip";
|
||||
import DirectCheckbox from "../components/DirectCheckbox";
|
||||
import EnterKnsName from "../components/EnterKnsName";
|
||||
|
||||
import { useAccount, usePublicClient, useWaitForTransactionReceipt, useWriteContract } from "wagmi";
|
||||
import { useAccount, useWaitForTransactionReceipt, useWriteContract } from "wagmi";
|
||||
import { useConnectModal, useAddRecentTransaction } from "@rainbow-me/rainbowkit";
|
||||
import { kinohash } from "../utils/kinohash";
|
||||
|
||||
import { NAME_URL, NAME_INVALID_PUNY, NAME_NOT_OWNER, NAME_NOT_REGISTERED } from "../components/EnterKnsName";
|
||||
|
||||
interface ResetProps extends PageProps { }
|
||||
|
||||
@ -28,7 +22,6 @@ function ResetKnsName({
|
||||
setDirect,
|
||||
setReset,
|
||||
knsName,
|
||||
setOsName,
|
||||
setNetworkingKey,
|
||||
setIpAddress,
|
||||
setWsPort,
|
||||
@ -37,7 +30,6 @@ function ResetKnsName({
|
||||
}: ResetProps) {
|
||||
const { address } = useAccount();
|
||||
const navigate = useNavigate();
|
||||
const client = usePublicClient();
|
||||
const { openConnectModal } = useConnectModal();
|
||||
|
||||
const { data: hash, writeContract, isPending, isError, error } = useWriteContract({
|
||||
@ -54,7 +46,6 @@ function ResetKnsName({
|
||||
const addRecentTransaction = useAddRecentTransaction();
|
||||
|
||||
const [name, setName] = useState<string>(knsName.slice(0, -3));
|
||||
const [nameVets, setNameVets] = useState<string[]>([]);
|
||||
const [nameValidities, setNameValidities] = useState<string[]>([])
|
||||
const [tba, setTba] = useState<string>("");
|
||||
const [triggerNameCheck, setTriggerNameCheck] = useState<boolean>(false);
|
||||
@ -67,80 +58,11 @@ function ResetKnsName({
|
||||
// so inputs will validate once wallet is connected
|
||||
useEffect(() => setTriggerNameCheck(!triggerNameCheck), [address]); // eslint-disable-line react-hooks/exhaustive-deps
|
||||
|
||||
|
||||
// TODO: separate this whole namechecking thing into helper function
|
||||
// boolean to branch whether to check for occupied or to match against our_address.
|
||||
|
||||
const nameDebouncer = useRef<NodeJS.Timeout | null>(null);
|
||||
useEffect(() => {
|
||||
if (nameDebouncer.current) clearTimeout(nameDebouncer.current);
|
||||
|
||||
nameDebouncer.current = setTimeout(async () => {
|
||||
setNameVets([]);
|
||||
|
||||
|
||||
if (name === "") return;
|
||||
|
||||
let index: number;
|
||||
let vets = [...nameVets];
|
||||
|
||||
let normalized: string;
|
||||
index = vets.indexOf(NAME_INVALID_PUNY);
|
||||
try {
|
||||
normalized = toAscii(name + ".os");
|
||||
if (index !== -1) vets.splice(index, 1);
|
||||
} catch (e) {
|
||||
if (index === -1) vets.push(NAME_INVALID_PUNY);
|
||||
}
|
||||
|
||||
// only check if name is valid punycode
|
||||
if (normalized! !== undefined) {
|
||||
index = vets.indexOf(NAME_URL);
|
||||
if (name !== "" && !isValidDomain(normalized)) {
|
||||
if (index === -1) vets.push(NAME_URL);
|
||||
} else if (index !== -1) vets.splice(index, 1);
|
||||
|
||||
try {
|
||||
const namehash = kinohash(normalized)
|
||||
console.log('normalized', normalized)
|
||||
console.log('namehash', namehash)
|
||||
// maybe separate into helper function for readability?
|
||||
// also note picking the right chain ID & address!
|
||||
const data = await client?.readContract({
|
||||
address: KINOMAP,
|
||||
abi: kinomapAbi,
|
||||
functionName: "get",
|
||||
args: [namehash]
|
||||
})
|
||||
const tba = data?.[0];
|
||||
const owner = data?.[1];
|
||||
|
||||
|
||||
console.log('GOT data', data)
|
||||
console.log('GOT tba', tba)
|
||||
|
||||
index = vets.indexOf(NAME_NOT_OWNER);
|
||||
if (owner === address && index !== -1) vets.splice(index, 1);
|
||||
else if (index === -1 && owner !== address)
|
||||
vets.push(NAME_NOT_OWNER);
|
||||
|
||||
index = vets.indexOf(NAME_NOT_REGISTERED);
|
||||
if (index !== -1) vets.splice(index, 1);
|
||||
|
||||
if (tba !== undefined) {
|
||||
setTba(tba);
|
||||
}
|
||||
} catch (e) {
|
||||
index = vets.indexOf(NAME_NOT_REGISTERED);
|
||||
if (index === -1) vets.push(NAME_NOT_REGISTERED);
|
||||
}
|
||||
|
||||
if (nameVets.length === 0) setOsName(normalized);
|
||||
}
|
||||
|
||||
setNameVets(vets);
|
||||
}, 500);
|
||||
}, [name, triggerNameCheck]); // eslint-disable-line react-hooks/exhaustive-deps
|
||||
if (!address) {
|
||||
openConnectModal?.();
|
||||
}
|
||||
}, [address, openConnectModal]);
|
||||
|
||||
const handleResetRecords = useCallback(
|
||||
async (e: FormEvent) => {
|
||||
@ -152,8 +74,6 @@ function ResetKnsName({
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
|
||||
try {
|
||||
const data = await generateNetworkingKeys({
|
||||
direct,
|
||||
@ -167,10 +87,6 @@ function ResetKnsName({
|
||||
reset: true,
|
||||
});
|
||||
|
||||
console.log('data', data)
|
||||
|
||||
console.log('tba', tba)
|
||||
|
||||
writeContract({
|
||||
address: tba as `0x${string}`,
|
||||
abi: mechAbi,
|
||||
@ -202,19 +118,22 @@ function ResetKnsName({
|
||||
return (
|
||||
<div className="container fade-in">
|
||||
<div className="section">
|
||||
{Boolean(address) && (
|
||||
{
|
||||
<form className="form" onSubmit={handleResetRecords}>
|
||||
{isPending || isConfirming ? (
|
||||
<Loader msg={isConfirming ? "Resetting Networking Information..." : "Please confirm the transaction in your wallet"} />
|
||||
) : (
|
||||
<>
|
||||
<h3 className="form-label">
|
||||
<Tooltip text="Kinodes use a .os name in order to identify themselves to other nodes in the network.">
|
||||
Specify the node ID to reset
|
||||
<Tooltip text="Kinodes use an onchain username in order to identify themselves to other nodes in the network.">
|
||||
Node ID to reset:
|
||||
</Tooltip>
|
||||
</h3>
|
||||
<EnterKnsName {...{ name, setName, nameVets, triggerNameCheck, nameValidities, setNameValidities, isReset: true }} />
|
||||
<EnterKnsName {...{ address, name, setName, triggerNameCheck, nameValidities, setNameValidities, setTba, isReset: true }} />
|
||||
<DirectCheckbox {...{ direct, setDirect }} />
|
||||
<p>
|
||||
A reset will not delete any data. It only updates the networking information that your node publishes onchain.
|
||||
</p>
|
||||
<button
|
||||
type="submit"
|
||||
className="button mt-2"
|
||||
@ -230,7 +149,7 @@ function ResetKnsName({
|
||||
</p>
|
||||
)}
|
||||
</form>
|
||||
)}
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
@ -4,7 +4,7 @@ import { downloadKeyfile } from "../utils/download-keyfile";
|
||||
import { Tooltip } from "../components/Tooltip";
|
||||
import { sha256, toBytes } from "viem";
|
||||
import { useSignTypedData, useAccount, useChainId } from 'wagmi'
|
||||
import { KINOMAP } from "../abis";
|
||||
import { KIMAP } from "../abis";
|
||||
|
||||
type SetPasswordProps = {
|
||||
direct: boolean;
|
||||
@ -61,7 +61,7 @@ function SetPassword({
|
||||
name: "Kimap",
|
||||
version: "1",
|
||||
chainId: chainId,
|
||||
verifyingContract: KINOMAP,
|
||||
verifyingContract: KIMAP,
|
||||
},
|
||||
types: {
|
||||
Boot: [
|
||||
@ -111,7 +111,6 @@ function SetPassword({
|
||||
res.status < 300 &&
|
||||
Number(res.headers.get("content-length")) !== appSizeOnLoad
|
||||
) {
|
||||
console.log("WE GOOD, ROUTING")
|
||||
clearInterval(interval);
|
||||
window.location.replace("/");
|
||||
}
|
||||
@ -132,8 +131,8 @@ function SetPassword({
|
||||
) : (
|
||||
<form className="form" onSubmit={handleSubmit}>
|
||||
<div className="form-group">
|
||||
<Tooltip text="This password will be used to log in if you restart your node or switch browsers.">
|
||||
<label className="form-label" htmlFor="password">New Password</label>
|
||||
<Tooltip text="This password will be used to log in when you restart your node or switch browsers.">
|
||||
<label className="form-label" htmlFor="password">Set Password</label>
|
||||
</Tooltip>
|
||||
<input
|
||||
type="password"
|
||||
@ -141,7 +140,7 @@ function SetPassword({
|
||||
required
|
||||
minLength={6}
|
||||
name="password"
|
||||
placeholder="Min 6 characters"
|
||||
placeholder="6 characters minimum"
|
||||
value={pw}
|
||||
onChange={(e) => setPw(e.target.value)}
|
||||
autoFocus
|
||||
@ -155,7 +154,7 @@ function SetPassword({
|
||||
required
|
||||
minLength={6}
|
||||
name="confirm-password"
|
||||
placeholder="Min 6 characters"
|
||||
placeholder="6 characters minimum"
|
||||
value={pw2}
|
||||
onChange={(e) => setPw2(e.target.value)}
|
||||
/>
|
||||
|
@ -105,6 +105,7 @@ pub async fn register(
|
||||
.or(warp::path("reset"))
|
||||
.or(warp::path("import-keyfile"))
|
||||
.or(warp::path("set-password"))
|
||||
.or(warp::path("custom-register"))
|
||||
.and(warp::get())
|
||||
.map(move |_| warp::reply::html(include_str!("register-ui/build/index.html")));
|
||||
|
||||
@ -347,7 +348,6 @@ async fn handle_boot(
|
||||
// this call can fail if the indexer has not caught up to the transaction
|
||||
// that just got confirmed on our frontend. for this reason, we retry
|
||||
// the call a few times before giving up.
|
||||
// todo remove?
|
||||
|
||||
let mut attempts = 0;
|
||||
let mut get_result = Err(());
|
||||
@ -443,6 +443,7 @@ async fn handle_import_keyfile(
|
||||
sender: Arc<RegistrationSender>,
|
||||
provider: Arc<RootProvider<PubSubFrontend>>,
|
||||
) -> Result<impl Reply, Rejection> {
|
||||
println!("received base64 keyfile: {}\r", info.keyfile);
|
||||
// if keyfile was not present in node and is present from user upload
|
||||
let encoded_keyfile = match base64_standard.decode(info.keyfile) {
|
||||
Ok(k) => k,
|
||||
@ -455,6 +456,10 @@ async fn handle_import_keyfile(
|
||||
}
|
||||
};
|
||||
|
||||
println!(
|
||||
"received keyfile: {}\r",
|
||||
String::from_utf8_lossy(&encoded_keyfile)
|
||||
);
|
||||
let (decoded_keyfile, mut our) =
|
||||
match keygen::decode_keyfile(&encoded_keyfile, &info.password_hash) {
|
||||
Ok(k) => {
|
||||
|
@ -132,7 +132,10 @@ pub async fn terminal(
|
||||
// the kernel will try and print all events by default so that booting with
|
||||
// verbosity mode 3 guarantees all events from boot are shown.
|
||||
if verbose_mode != 3 {
|
||||
let _ = debug_event_loop.send(DebugCommand::ToggleEventLoop).await;
|
||||
debug_event_loop
|
||||
.send(DebugCommand::ToggleEventLoop)
|
||||
.await
|
||||
.expect("failed to toggle full event loop off");
|
||||
}
|
||||
|
||||
// only create event stream if not in detached mode
|
||||
@ -336,8 +339,21 @@ async fn handle_event(
|
||||
match verbose_mode {
|
||||
0 => *verbose_mode = 1,
|
||||
1 => *verbose_mode = 2,
|
||||
2 => *verbose_mode = 3,
|
||||
_ => *verbose_mode = 0,
|
||||
2 => {
|
||||
*verbose_mode = 3;
|
||||
debug_event_loop
|
||||
.send(DebugCommand::ToggleEventLoop)
|
||||
.await
|
||||
.expect("failed to toggle ON full event loop");
|
||||
}
|
||||
3 => {
|
||||
*verbose_mode = 0;
|
||||
debug_event_loop
|
||||
.send(DebugCommand::ToggleEventLoop)
|
||||
.await
|
||||
.expect("failed to toggle OFF full event loop");
|
||||
}
|
||||
_ => unreachable!(),
|
||||
}
|
||||
Printout::new(
|
||||
0,
|
||||
@ -347,15 +363,13 @@ async fn handle_event(
|
||||
0 => "off",
|
||||
1 => "debug",
|
||||
2 => "super-debug",
|
||||
_ => "full event loop",
|
||||
3 => "full event loop",
|
||||
_ => unreachable!(),
|
||||
}
|
||||
),
|
||||
)
|
||||
.send(&print_tx)
|
||||
.await;
|
||||
if *verbose_mode == 3 {
|
||||
let _ = debug_event_loop.send(DebugCommand::ToggleEventLoop).await;
|
||||
}
|
||||
}
|
||||
//
|
||||
// CTRL+J: toggle debug mode -- makes system-level event loop step-through
|
||||
|
@ -1299,8 +1299,9 @@ impl Printout {
|
||||
}
|
||||
}
|
||||
|
||||
/// Fire the printout to the terminal without checking for success.
|
||||
pub async fn send(self, sender: &PrintSender) {
|
||||
sender.send(self).await.expect("print sender died");
|
||||
let _ = sender.send(self).await;
|
||||
}
|
||||
}
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user