mirror of
https://github.com/onsonr/hway.git
synced 2025-03-10 04:57:08 +00:00
feat: add Sonr Communication Infrastructure
This commit is contained in:
parent
7fef4a8522
commit
951dc860d3
@ -1,3 +1,6 @@
|
|||||||
|
|
||||||
|
|
||||||
|
# For goreleaser
|
||||||
FROM scratch
|
FROM scratch
|
||||||
ENTRYPOINT ["/hway"]
|
ENTRYPOINT ["/hway"]
|
||||||
COPY hway /
|
COPY hway /
|
||||||
|
4
deploy/README.md
Normal file
4
deploy/README.md
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
# `matrix`
|
||||||
|
|
||||||
|
Sonr Communication infrastructure which allows for Sonr Blockchain
|
||||||
|
Identities to have a secure and private communication channel.
|
6
deploy/gateway/Dockerfile
Normal file
6
deploy/gateway/Dockerfile
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
|
||||||
|
|
||||||
|
# For goreleaser
|
||||||
|
FROM scratch
|
||||||
|
ENTRYPOINT ["/hway"]
|
||||||
|
COPY hway /
|
6
deploy/matrix/Dockerfile
Normal file
6
deploy/matrix/Dockerfile
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
|
||||||
|
|
||||||
|
# For goreleaser
|
||||||
|
FROM scratch
|
||||||
|
ENTRYPOINT ["/hway"]
|
||||||
|
COPY hway /
|
5
deploy/matrix/bootstrap.sh
Executable file
5
deploy/matrix/bootstrap.sh
Executable file
@ -0,0 +1,5 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
|
set -e
|
||||||
|
|
||||||
|
|
24
deploy/matrix/devbox.json
Normal file
24
deploy/matrix/devbox.json
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
{
|
||||||
|
"$schema": "https://raw.githubusercontent.com/jetify-com/devbox/0.13.7/.schema/devbox.schema.json",
|
||||||
|
"packages": [
|
||||||
|
"go@latest",
|
||||||
|
"cargo@latest",
|
||||||
|
"uv@latest",
|
||||||
|
"bun@latest"
|
||||||
|
],
|
||||||
|
"env": {
|
||||||
|
"PATH": "$HOME/.cargo/bin:$HOME/go/bin:$HOME/.local/bin:$HOME/.bun/bin:$PATH",
|
||||||
|
"GITHUB_TOKEN": "$GITHUB_TOKEN",
|
||||||
|
"GOPATH": "$HOME/go",
|
||||||
|
"GOBIN": "$GOPATH/bin",
|
||||||
|
"GHQ_ROOT": "$CLONEDIR"
|
||||||
|
},
|
||||||
|
"shell": {
|
||||||
|
"init_hook": [],
|
||||||
|
"scripts": {
|
||||||
|
"test": [
|
||||||
|
"echo \"Error: no test specified\" && exit 1"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
38
x/register/islands/card_account.templ
Normal file
38
x/register/islands/card_account.templ
Normal file
@ -0,0 +1,38 @@
|
|||||||
|
package islands
|
||||||
|
|
||||||
|
templ CardAccount(addr, name, handle, creationBlock string) {
|
||||||
|
<div class="profile-card min-w-[320px]">
|
||||||
|
<div class="text-white max-w-xs my-auto mx-auto bg-gradient-to-r from-cyan-600 to-cyan-300 p-4 py-5 px-5 rounded-xl">
|
||||||
|
<div class="flex justify-between">
|
||||||
|
<div>
|
||||||
|
<h2>sonr-testnet-1</h2>
|
||||||
|
<p class="text-2xl font-bold">{ handle }</p>
|
||||||
|
</div>
|
||||||
|
<div class="flex items-center opacity-60">
|
||||||
|
<sl-icon style="font-size: 52px;" library="sonr" name="sonr-fill"></sl-icon>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="mt-5 flex justify-between items-center w-52">
|
||||||
|
<span class="text-lg font-mono">{ shortenAddress(addr) }</span>
|
||||||
|
</div>
|
||||||
|
<div class="flex justify-between mt-5 w-48 ">
|
||||||
|
<div>
|
||||||
|
<h3 class="text-xs">Block Created </h3>
|
||||||
|
<p class="font-bold"><span>#</span>{ creationBlock }</p>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<h3 class="text-xs">Issued to</h3>
|
||||||
|
<p class="font-bold">{ name }</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
|
||||||
|
// Helper function to shorten address
|
||||||
|
func shortenAddress(address string) string {
|
||||||
|
if len(address) <= 20 {
|
||||||
|
return address
|
||||||
|
}
|
||||||
|
return address[:16] + "..." + address[len(address)-4:]
|
||||||
|
}
|
70
x/register/islands/coin_select.templ
Normal file
70
x/register/islands/coin_select.templ
Normal file
@ -0,0 +1,70 @@
|
|||||||
|
package islands
|
||||||
|
|
||||||
|
type Coin struct {
|
||||||
|
Ticker string
|
||||||
|
Name string
|
||||||
|
IsDefault bool
|
||||||
|
}
|
||||||
|
|
||||||
|
var defaultCoins = []Coin{
|
||||||
|
{Ticker: "SNR", Name: "Sonr", IsDefault: true},
|
||||||
|
{Ticker: "BTC", Name: "Bitcoin", IsDefault: true},
|
||||||
|
{Ticker: "ETH", Name: "Ethereum", IsDefault: true},
|
||||||
|
{Ticker: "SOL", Name: "Solana", IsDefault: false},
|
||||||
|
{Ticker: "LTC", Name: "Litecoin", IsDefault: false},
|
||||||
|
{Ticker: "DOGE", Name: "Dogecoin", IsDefault: false},
|
||||||
|
{Ticker: "XRP", Name: "Ripple", IsDefault: false},
|
||||||
|
{Ticker: "OSMO", Name: "Osmosis", IsDefault: false},
|
||||||
|
{Ticker: "ATOM", Name: "Cosmos", IsDefault: false},
|
||||||
|
{Ticker: "STARZ", Name: "Stargaze", IsDefault: false},
|
||||||
|
{Ticker: "AKT", Name: "Akash", IsDefault: false},
|
||||||
|
{Ticker: "EVMOS", Name: "Evmos", IsDefault: false},
|
||||||
|
{Ticker: "FIL", Name: "Filecoin", IsDefault: false},
|
||||||
|
{Ticker: "AXL", Name: "Axelar", IsDefault: false},
|
||||||
|
}
|
||||||
|
|
||||||
|
templ CoinSelect() {
|
||||||
|
<sl-select
|
||||||
|
label="Accounts"
|
||||||
|
name="selected_assets"
|
||||||
|
value="SNR BTC ETH"
|
||||||
|
help-text="Select Blockchains to connect with your Vault"
|
||||||
|
multiple
|
||||||
|
class="custom-tag py-2"
|
||||||
|
>
|
||||||
|
for _, a := range defaultCoins {
|
||||||
|
@CoinOption(a)
|
||||||
|
}
|
||||||
|
</sl-select>
|
||||||
|
<script>
|
||||||
|
const select = document.querySelector('.custom-tag');
|
||||||
|
select.getTag = (option, index) => {
|
||||||
|
// Use the same icon used in the <sl-option>
|
||||||
|
const name = option.querySelector('sl-icon[slot="prefix"]').name;
|
||||||
|
|
||||||
|
// You can return a string, a Lit Template, or an HTMLElement here
|
||||||
|
return `
|
||||||
|
<sl-tag removable>
|
||||||
|
<sl-icon name="${name}" library="crypto" style="padding-inline-end: .5rem;"></sl-icon>
|
||||||
|
${option.getTextLabel()}
|
||||||
|
</sl-tag>
|
||||||
|
`;
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
}
|
||||||
|
|
||||||
|
templ CoinOption(a Coin) {
|
||||||
|
if a.IsDefault {
|
||||||
|
<sl-option value={ a.Ticker } selected disabled>
|
||||||
|
<sl-icon slot="prefix" name={ a.Ticker } library="crypto"></sl-icon>
|
||||||
|
{ a.Name }
|
||||||
|
</sl-option>
|
||||||
|
<sl-divider></sl-divider>
|
||||||
|
} else {
|
||||||
|
<sl-option value={ a.Ticker }>
|
||||||
|
<sl-icon slot="prefix" name={ a.Ticker } library="crypto"></sl-icon>
|
||||||
|
{ a.Name }
|
||||||
|
</sl-option>
|
||||||
|
<sl-divider></sl-divider>
|
||||||
|
}
|
||||||
|
}
|
25
x/register/islands/human_slider.templ
Normal file
25
x/register/islands/human_slider.templ
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
package islands
|
||||||
|
|
||||||
|
import "fmt"
|
||||||
|
|
||||||
|
templ HumanSlider(firstNumber int, lastNumber int) {
|
||||||
|
<div hx-target="this" hx-swap="outerHTML">
|
||||||
|
<sl-range name="is_human" label={ humanLabel(firstNumber, lastNumber) } help-text="Prove you are a human." min="0" max="9" step="1" hx-post="/register/profile/is_human"></sl-range>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
|
||||||
|
templ HumanSliderError(firstNumber int, lastNumber int) {
|
||||||
|
<sl-range name="is_human" label={ humanLabel(firstNumber, lastNumber) } help-text="Prove you are a human." min="0" max="9" step="1"></sl-range>
|
||||||
|
<div slot="help-text">
|
||||||
|
<sl-icon name="x-lg" library="sonr"></sl-icon>
|
||||||
|
Invalid Human Sum
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
|
||||||
|
templ HumanSliderSuccess() {
|
||||||
|
<sl-range name="is_human" label="Success! Welcome Human." help-text="Prove you are a human." min="0" max="9" step="1" value="9" disabled></sl-range>
|
||||||
|
}
|
||||||
|
|
||||||
|
func humanLabel(firstNumber int, lastNumber int) string {
|
||||||
|
return fmt.Sprintf("What is %d + %d?", firstNumber, lastNumber)
|
||||||
|
}
|
45
x/register/islands/input_handle.templ
Normal file
45
x/register/islands/input_handle.templ
Normal file
@ -0,0 +1,45 @@
|
|||||||
|
package islands
|
||||||
|
|
||||||
|
type HandleState string
|
||||||
|
|
||||||
|
const (
|
||||||
|
HandleStateInitial HandleState = "inital"
|
||||||
|
HandleStateValid HandleState = "valid"
|
||||||
|
HandleStateInvalid HandleState = "invalid"
|
||||||
|
)
|
||||||
|
|
||||||
|
func (s HandleState) string() string {
|
||||||
|
return string(s)
|
||||||
|
}
|
||||||
|
|
||||||
|
templ InputHandle() {
|
||||||
|
<div hx-target="this" hx-swap="outerHTML">
|
||||||
|
<sl-input name="handle" placeholder="digitalgold" type="text" label="Handle" minlength="4" maxlength="12" required hx-post="/register/profile" hx-indicator="#handle-indicator" autofocus>
|
||||||
|
<div slot="prefix">
|
||||||
|
<sl-icon name="at-sign" library="sonr"></sl-icon>
|
||||||
|
</div>
|
||||||
|
</sl-input>
|
||||||
|
</div>
|
||||||
|
<br/>
|
||||||
|
}
|
||||||
|
|
||||||
|
templ InputHandleError(value string, helpText string) {
|
||||||
|
<sl-input name="handle" placeholder="digitalgold" type="text" label="Handle" minlength="4" maxlength="12" required class="border-red-500" value={ value } help-text={ helpText }>
|
||||||
|
<div slot="prefix">
|
||||||
|
<sl-icon name="at-sign" library="sonr"></sl-icon>
|
||||||
|
</div>
|
||||||
|
<div slot="suffix" style="color: #B54549;">
|
||||||
|
<sl-icon name="x"></sl-icon>
|
||||||
|
</div>
|
||||||
|
</sl-input>
|
||||||
|
<br/>
|
||||||
|
}
|
||||||
|
|
||||||
|
templ InputHandleSuccess(value string) {
|
||||||
|
<sl-input name="handle" placeholder="digitalgold" type="text" label="Handle" minlength="4" maxlength="12" required class="border-green-500" value={ value } disabled>
|
||||||
|
<div slot="prefix" style="color: #46A758;">
|
||||||
|
<sl-icon name="at-sign" library="sonr"></sl-icon>
|
||||||
|
</div>
|
||||||
|
</sl-input>
|
||||||
|
<br/>
|
||||||
|
}
|
97
x/register/islands/input_passkey.templ
Normal file
97
x/register/islands/input_passkey.templ
Normal file
@ -0,0 +1,97 @@
|
|||||||
|
package islands
|
||||||
|
|
||||||
|
templ InputPasskey(addr string, userHandle string, challenge string) {
|
||||||
|
<sl-button style="width: 100%;" onclick={ navigatorCredentialsCreate(addr, userHandle, challenge) }>
|
||||||
|
<sl-icon slot="prefix" name="passkey" library="sonr" style="font-size: 24px;" class="text-neutral-500"></sl-icon>
|
||||||
|
Register Passkey
|
||||||
|
</sl-button>
|
||||||
|
}
|
||||||
|
|
||||||
|
script navigatorCredentialsCreate(userId string, userHandle string, challenge string) {
|
||||||
|
const publicKey = {
|
||||||
|
challenge: Uint8Array.from(challenge, (c) => c.charCodeAt(0)),
|
||||||
|
rp: {
|
||||||
|
name: "Sonr.ID",
|
||||||
|
},
|
||||||
|
user: {
|
||||||
|
// Assuming that userId is ASCII-only
|
||||||
|
id: Uint8Array.from(userId, (c) => c.charCodeAt(0)),
|
||||||
|
name: userId,
|
||||||
|
displayName: userHandle,
|
||||||
|
},
|
||||||
|
pubKeyCredParams: [
|
||||||
|
{
|
||||||
|
type: "public-key",
|
||||||
|
alg: -7, // "ES256"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: "public-key",
|
||||||
|
alg: -257, // "RS256"
|
||||||
|
},
|
||||||
|
],
|
||||||
|
authenticatorSelection: {
|
||||||
|
userVerification: "required",
|
||||||
|
residentKey: "required",
|
||||||
|
authenticatorAttachment: "platform",
|
||||||
|
},
|
||||||
|
timeout: 60000, // 1 minute
|
||||||
|
extensions: {
|
||||||
|
payment: {
|
||||||
|
isPayment: true,
|
||||||
|
},
|
||||||
|
largeBlob: {
|
||||||
|
supported: "preferred",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
// Helper function to convert ArrayBuffer to Base64URL string
|
||||||
|
function arrayBufferToBase64URL(buffer) {
|
||||||
|
const bytes = new Uint8Array(buffer);
|
||||||
|
let str = '';
|
||||||
|
bytes.forEach(byte => { str += String.fromCharCode(byte) });
|
||||||
|
return btoa(str)
|
||||||
|
.replace(/\+/g, '-')
|
||||||
|
.replace(/\//g, '_')
|
||||||
|
.replace(/=/g, '');
|
||||||
|
}
|
||||||
|
|
||||||
|
navigator.credentials
|
||||||
|
.create({ publicKey })
|
||||||
|
.then((newCredentialInfo) => {
|
||||||
|
if (!(newCredentialInfo instanceof PublicKeyCredential)) {
|
||||||
|
throw new Error('Received credential is not a PublicKeyCredential');
|
||||||
|
}
|
||||||
|
|
||||||
|
const response = newCredentialInfo.response;
|
||||||
|
if (!(response instanceof AuthenticatorAttestationResponse)) {
|
||||||
|
throw new Error('Response is not an AuthenticatorAttestationResponse');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Convert the credential data to a cross-platform compatible format
|
||||||
|
const credentialJSON = {
|
||||||
|
id: newCredentialInfo.id,
|
||||||
|
rawId: arrayBufferToBase64URL(newCredentialInfo.rawId),
|
||||||
|
type: newCredentialInfo.type,
|
||||||
|
authenticatorAttachment: newCredentialInfo.authenticatorAttachment || null,
|
||||||
|
transports: Array.isArray(response.getTransports) ? response.getTransports() : [],
|
||||||
|
clientExtensionResults: newCredentialInfo.getClientExtensionResults(),
|
||||||
|
response: {
|
||||||
|
attestationObject: arrayBufferToBase64URL(response.attestationObject),
|
||||||
|
clientDataJSON: arrayBufferToBase64URL(response.clientDataJSON)
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Set the form value with the stringified credential data
|
||||||
|
const credential = document.getElementById('credential-data');
|
||||||
|
credential.value = JSON.stringify(credentialJSON);
|
||||||
|
|
||||||
|
// Submit the form
|
||||||
|
const form = document.getElementById('passkey-form');
|
||||||
|
form.submit();
|
||||||
|
})
|
||||||
|
.catch((err) => {
|
||||||
|
console.error('Passkey creation failed:', err);
|
||||||
|
alert(`Failed to create passkey: ${err.message || 'Unknown error'}`);
|
||||||
|
});
|
||||||
|
}
|
@ -1 +0,0 @@
|
|||||||
package handlers
|
|
@ -1 +0,0 @@
|
|||||||
package handlers
|
|
Loading…
x
Reference in New Issue
Block a user