Skip to content

Commit

Permalink
wip
Browse files Browse the repository at this point in the history
  • Loading branch information
alecdwm committed Aug 23, 2024
1 parent 9bcf491 commit 2c0d6be
Show file tree
Hide file tree
Showing 12 changed files with 295 additions and 52 deletions.
Binary file modified bun.lockb
Binary file not shown.
10 changes: 6 additions & 4 deletions index.html
Original file line number Diff line number Diff line change
@@ -1,13 +1,15 @@
<!doctype html>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Vite + React + TS</title>
<title>Laos Signature Test</title>
</head>
<body>
<div id="root"></div>
<body
class="m-0 flex min-h-screen min-w-[320px] place-items-center dark:bg-gray-900 dark:text-gray-100"
>
<div id="root" class="mx-auto max-w-screen-xl p-8"></div>
<script type="module" src="/src/main.tsx"></script>
</body>
</html>
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
},
"devDependencies": {
"@eslint/js": "^9.9.0",
"@tailwindcss/forms": "^0.5.7",
"@types/react": "^18.3.3",
"@types/react-dom": "^18.3.0",
"@vitejs/plugin-react-swc": "^3.5.0",
Expand Down
153 changes: 128 additions & 25 deletions src/App.tsx
Original file line number Diff line number Diff line change
@@ -1,34 +1,137 @@
import { useState } from "react"
import { ApiPromise, Keyring, WsProvider } from "@polkadot/api"
import { Metadata } from "@polkadot/types"
import { useCallback, useEffect, useState } from "react"

const presetWsUrls = [
"wss://rpc.laos.laosfoundation.io",
"wss://rpc.laosmercury.gorengine.com",
"wss://wss.api.moonbeam.network",
]
import { presetWsUrls } from "./constants"
import { SeedInput } from "./SeedInput"
import { metadataFromOpaque } from "./util"
import { WsSelect } from "./WsSelect"

function App() {
const [wsUrl, setWsUrl] = useState(presetWsUrls[0])
const keyring = new Keyring()

const defaultSeed = "test test test test test test test test test test test junk"
const defaultWsUrl = presetWsUrls[0]

export const App = () => {
const [seed, setSeed] = useState(defaultSeed)
const [wsUrl, setWsUrl] = useState(defaultWsUrl)

const [address, setAddress] = useState("")
useEffect(() => {
try {
const keypair = keyring.createFromUri(`${seed}/m/44'/60'/0'/0/0`, {}, "ethereum")
setAddress(keypair.address.toString())
} catch {
setAddress("")
}
}, [seed])

const [balance, setBalance] = useState<string | null>(null)
useEffect(() => {
try {
const keypair = keyring.createFromUri(`${seed}/m/44'/60'/0'/0/0`, {}, "ethereum")

const abort = new AbortController()

const api = new ApiPromise({ provider: new WsProvider(wsUrl) })
abort.signal.onabort = () => api.disconnect()

setBalance("loading")

//
;(async () => {
try {
await api.isReadyOrError
if (abort.signal.aborted) return

const balance = await api.query.system.account(keypair.address)

// eslint-disable-next-line @typescript-eslint/no-explicit-any
setBalance((balance as any)?.data?.free?.toString?.() ?? "0")
} catch {
setBalance(null)
}
})()

return () => abort.abort()
} catch {
setBalance(null)
}
}, [seed, wsUrl])

const [output, setOutput] = useState("")
const addOutput = useCallback((data: string) => setOutput((o) => o + data + "\n"), [])

const test = useCallback(() => {
const keypair = keyring.createFromUri(`${seed}/m/44'/60'/0'/0/0`, {}, "ethereum")

const abort = new AbortController()

const api = new ApiPromise({ provider: new WsProvider(wsUrl) })
abort.signal.onabort = () => api.disconnect()

addOutput("Creating extrinsic...")

//
;(async () => {
try {
await api.isReadyOrError
if (abort.signal.aborted) return

const opaqueMetadata = await api.call.metadata.metadataAtVersion(15)
const metadataRpc = metadataFromOpaque(opaqueMetadata.toU8a())
// addOutput("metadataRpc: " + metadataRpc)

const metadata = new Metadata(api.registry, metadataRpc)
// addOutput("metadata: " + JSON.stringify(metadata.toJSON(), null, 2))

api.registry.setMetadata(metadata)

const extrinsic = await api.tx.balances
.transferKeepAlive(keypair.address, 0)
.signAsync(keypair)

addOutput(
`Extrinsic signature: ${extrinsic.signature}\n(length ${extrinsic.signature.byteLength})`
)
} catch (cause) {
addOutput(`Failed: ${String(cause)}`)
}
})()

return () => abort.abort()
}, [addOutput, seed, wsUrl])

return (
<>
<label htmlFor="wsurl">ws url</label>
<input
id="wsurl"
type="text"
<div className="flex min-w-[640px] flex-col gap-4">
<h1 className="text-xl">Laos Signature Test</h1>
<SeedInput
value={seed}
onClick={() => setSeed("")}
onChange={(e) => setSeed(e.currentTarget.value)}
onReset={() => setSeed(defaultSeed)}
/>
<div className="text-sm">
{address ? "Address: " : "Invalid seed or key"}
{address}
</div>
<div className="-mt-4 text-sm">
{balance !== null ? "Balance: " : "No balance"}
{balance}
</div>
<WsSelect
value={wsUrl}
onClick={() => setWsUrl("")}
onChange={(e) => setWsUrl(e.currentTarget.value)}
onReset={() => setWsUrl(defaultWsUrl)}
/>
<div></div>
<h1>Vite + React</h1>
<div className="card">
{/* <button onClick={() => setCount((count) => count + 1)}>count is {count}</button> */}
<p>
Edit <code>src/App.tsx</code> and save to test HMR
</p>
</div>
<p className="read-the-docs">Click on the Vite and React logos to learn more</p>
</>
<button
className="ocus:outline-none rounded border border-indigo-600 bg-indigo-600 px-12 py-3 text-sm font-medium text-white hover:text-gray-100 focus:ring active:opacity-85"
onClick={test}
>
Test
</button>
{output && <pre className="p-4 text-xs dark:bg-gray-800">{output}</pre>}
</div>
)
}

export default App
54 changes: 54 additions & 0 deletions src/SeedInput.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
export const SeedInput = ({
value,
onClick,
onChange,
onReset,
}: Pick<
React.DetailedHTMLProps<React.InputHTMLAttributes<HTMLInputElement>, HTMLInputElement>,
"value" | "onClick" | "onChange"
> & { onReset: () => void }) => (
<div>
<label htmlFor="wsUrl" className="block text-sm font-medium text-gray-900 dark:text-gray-200">
seedOrKey
</label>

<div className="relative mt-1.5">
<input
type="text"
id="seedOrKey"
className="w-full rounded-lg border-gray-300 p-2 pe-10 text-gray-700 sm:text-sm dark:bg-gray-800 dark:text-gray-300 [&::-webkit-calendar-picker-indicator]:opacity-0"
placeholder="Enter a seed or key"
value={value}
onClick={onClick}
onChange={onChange}
/>

<span className="absolute inset-y-0 end-2 flex w-8 items-center">
<svg
xmlns="http://www.w3.org/2000/svg"
fill="none"
viewBox="0 0 24 24"
strokeWidth="1.5"
stroke="currentColor"
className="size-5 text-gray-500"
/>
<svg
xmlns="http://www.w3.org/2000/svg"
fill="none"
viewBox="0 0 21 21"
fillRule="evenodd"
stroke="currentColor"
strokeLinecap="round"
strokeLinejoin="round"
className="size-5 cursor-pointer text-gray-500 hover:text-gray-300"
onClick={onReset}
>
<g transform="matrix(0 1 1 0 2.5 2.5)">
<path d="m3.98652376 1.07807068c-2.38377179 1.38514556-3.98652376 3.96636605-3.98652376 6.92192932 0 4.418278 3.581722 8 8 8s8-3.581722 8-8-3.581722-8-8-8" />
<path d="m4 1v4h-4" transform="matrix(1 0 0 -1 0 6)" />
</g>
</svg>
</span>
</div>
</div>
)
72 changes: 72 additions & 0 deletions src/WsSelect.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
import { presetWsUrls } from "./constants"

export const WsSelect = ({
value,
onClick,
onChange,
onReset,
}: Pick<
React.DetailedHTMLProps<React.InputHTMLAttributes<HTMLInputElement>, HTMLInputElement>,
"value" | "onClick" | "onChange"
> & { onReset: () => void }) => (
<div>
<label htmlFor="wsUrl" className="block text-sm font-medium text-gray-900 dark:text-gray-200">
wsUrl
</label>

<div className="relative mt-1.5">
<input
type="text"
list="presetWsUrls"
id="wsUrl"
className="w-full rounded-lg border-gray-300 p-2 pe-10 text-gray-700 sm:text-sm dark:bg-gray-800 dark:text-gray-300 [&::-webkit-calendar-picker-indicator]:opacity-0"
placeholder="Enter a websocket url"
value={value}
onClick={onClick}
onChange={onChange}
/>

<span className="absolute inset-y-0 end-2 flex w-8 items-center">
<svg
xmlns="http://www.w3.org/2000/svg"
fill="none"
viewBox="0 0 24 24"
strokeWidth="1.5"
stroke="currentColor"
className="size-5 text-gray-500"
>
<path
strokeLinecap="round"
strokeLinejoin="round"
d="M8.25 15L12 18.75 15.75 15m-7.5-6L12 5.25 15.75 9"
/>
</svg>

<svg
xmlns="http://www.w3.org/2000/svg"
fill="none"
viewBox="0 0 21 21"
fillRule="evenodd"
stroke="currentColor"
strokeLinecap="round"
strokeLinejoin="round"
className="size-5 cursor-pointer text-gray-500 hover:text-gray-300"
onClick={onReset}
>
<g transform="matrix(0 1 1 0 2.5 2.5)">
<path d="m3.98652376 1.07807068c-2.38377179 1.38514556-3.98652376 3.96636605-3.98652376 6.92192932 0 4.418278 3.581722 8 8 8s8-3.581722 8-8-3.581722-8-8-8" />
<path d="m4 1v4h-4" transform="matrix(1 0 0 -1 0 6)" />
</g>
</svg>
</span>
</div>

<datalist id="presetWsUrls">
{presetWsUrls.map((url) => (
<option key={url} value={url}>
{url}
</option>
))}
</datalist>
</div>
)
1 change: 0 additions & 1 deletion src/assets/react.svg

This file was deleted.

5 changes: 5 additions & 0 deletions src/constants.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
export const presetWsUrls = [
"wss://rpc.laos.laosfoundation.io",
"wss://rpc.laosmercury.gorengine.com",
"wss://wss.api.moonbeam.network",
]
15 changes: 0 additions & 15 deletions src/index.css
Original file line number Diff line number Diff line change
@@ -1,18 +1,3 @@
@tailwind base;
@tailwind components;
@tailwind utilities;

#root {
max-width: 1280px;
margin: 0 auto;
padding: 2rem;
text-align: center;
}

body {
margin: 0;
display: flex;
place-items: center;
min-width: 320px;
min-height: 100vh;
}
14 changes: 8 additions & 6 deletions src/main.tsx
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
import { StrictMode } from 'react'
import { createRoot } from 'react-dom/client'
import App from './App.tsx'
import './index.css'
import { StrictMode } from "react"
import { createRoot } from "react-dom/client"

createRoot(document.getElementById('root')!).render(
import { App } from "./App.tsx"

import "./index.css"

createRoot(document.getElementById("root")!).render(
<StrictMode>
<App />
</StrictMode>,
</StrictMode>
)
20 changes: 20 additions & 0 deletions src/util.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import { TypeRegistry } from "@polkadot/types"
import { u8aToNumber } from "@polkadot/util"

export const metadataFromOpaque = (opaque: Uint8Array) => {
try {
// pjs codec for OpaqueMetadata doesn't allow us to decode the actual Metadata, find it ourselves
const u8aBytes = opaque
for (let i = 0; i < 20; i++) {
// skip until we find the magic number that is used as prefix of metadata objects (usually in the first 10 bytes)
if (u8aToNumber(u8aBytes.slice(i, i + 4)) !== 0x6174656d) continue

const metadata = new TypeRegistry().createType("Metadata", u8aBytes.slice(i))

return metadata.toHex()
}
throw new Error("Magic number not found")
} catch (cause) {
throw new Error("Failed to decode metadata from OpaqueMetadata", { cause })
}
}
2 changes: 1 addition & 1 deletion tailwind.config.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
/** @type {import('tailwindcss').Config} */
export default {
content: ["./src/**/*.{js,ts,jsx,tsx}"],
content: ["./index.html", "./src/**/*.{js,ts,jsx,tsx}"],
theme: {
extend: {},
},
Expand Down

0 comments on commit 2c0d6be

Please sign in to comment.