720 lines
24 KiB
HTML
720 lines
24 KiB
HTML
|
|
<!DOCTYPE html>
|
|||
|
|
<!--
|
|||
|
|
================================================================================
|
|||
|
|
TEMPL MIGRATION GUIDE: authorize.html → views/authorize.templ
|
|||
|
|
================================================================================
|
|||
|
|
|
|||
|
|
PAGE OVERVIEW:
|
|||
|
|
- OAuth/OIDC consent screen for third-party app authorization
|
|||
|
|
- Three request types via tabs: Connect, Sign Message, Transaction
|
|||
|
|
- Displays requesting app identity with verification badge
|
|||
|
|
- Shows wallet selector and permission scopes
|
|||
|
|
- Transaction tab includes swap preview, gas estimates, contract data
|
|||
|
|
- VIEWPORT: Optimized for popup/webview (360×540 to 480×680)
|
|||
|
|
|
|||
|
|
VIEWPORT CONSTRAINTS (Auth Popup/Webview):
|
|||
|
|
- Target sizes: 360×540 (small), 420×600 (medium), 480×680 (large)
|
|||
|
|
- Card max-width: 48ch (~384px) fits all viewport sizes
|
|||
|
|
- Tab panels scroll independently if content exceeds viewport
|
|||
|
|
- Transaction details use compact layout on small screens
|
|||
|
|
- Approve/Deny buttons always visible in footer
|
|||
|
|
|
|||
|
|
MAIN TEMPL COMPONENT:
|
|||
|
|
templ AuthorizePage(req AuthRequest, wallet WalletInfo) {
|
|||
|
|
@layouts.CenteredCard("Authorize - Sonr Motr Wallet") {
|
|||
|
|
@AppIdentityHeader(req.App)
|
|||
|
|
<wa-divider></wa-divider>
|
|||
|
|
@RequestTypeTabs(req)
|
|||
|
|
@AuthFooterActions(req.Type)
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
HTMX INTEGRATION:
|
|||
|
|
- Tab switching: hx-on:wa-tab-show="htmx.ajax('GET', '/authorize/tab/' + event.detail.name, '#tab-content')"
|
|||
|
|
- Approve action: hx-post="/api/authorize/approve" hx-include="[name='scopes']" hx-target="#auth-result"
|
|||
|
|
- Deny action: hx-post="/api/authorize/deny" hx-target="#auth-result"
|
|||
|
|
- Wallet selector: hx-get="/authorize/wallets" hx-target="#wallet-dropdown" hx-trigger="click"
|
|||
|
|
|
|||
|
|
SUB-COMPONENTS TO EXTRACT:
|
|||
|
|
- AppIdentityHeader(app AppInfo) // Logo, name, verified badge, domain
|
|||
|
|
- WalletSelector(wallets []Wallet, selected string)
|
|||
|
|
- PermissionItem(icon string, iconClass string, title string, desc string)
|
|||
|
|
- PermissionList(permissions []Permission)
|
|||
|
|
- MessagePreview(message string, signType string)
|
|||
|
|
- TransactionPreview(tx Transaction)
|
|||
|
|
- SwapPreview(from TokenAmount, to TokenAmount)
|
|||
|
|
- GasEstimate(network string, estimatedGas string, maxFee string)
|
|||
|
|
- ContractDataDetails(contract string, function string, data string)
|
|||
|
|
- AuthFooterActions(requestType string)
|
|||
|
|
- AuthResultSuccess(type string, txHash string)
|
|||
|
|
- AuthResultDenied()
|
|||
|
|
|
|||
|
|
STATE/PROPS:
|
|||
|
|
type AuthRequest struct {
|
|||
|
|
Type string // "connect", "sign", "transaction"
|
|||
|
|
App AppInfo
|
|||
|
|
Scopes []string
|
|||
|
|
Message string // For sign requests
|
|||
|
|
Transaction *TxDetails // For transaction requests
|
|||
|
|
State string // OAuth state param
|
|||
|
|
Nonce string // For OIDC
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
type AppInfo struct {
|
|||
|
|
Name string
|
|||
|
|
Domain string
|
|||
|
|
LogoURL string
|
|||
|
|
Verified bool
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
type TxDetails struct {
|
|||
|
|
Type string // "swap", "send", "contract"
|
|||
|
|
FromToken TokenAmount
|
|||
|
|
ToToken TokenAmount
|
|||
|
|
Network string
|
|||
|
|
EstimatedGas string
|
|||
|
|
MaxFee string
|
|||
|
|
Contract string
|
|||
|
|
Function string
|
|||
|
|
RawData string
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
HTMX PATTERNS:
|
|||
|
|
// Tab group with HTMX partial loading
|
|||
|
|
<wa-tab-group hx-on:wa-tab-show="
|
|||
|
|
htmx.ajax('GET', '/authorize/panel/' + event.detail.name, {target: '#panel-content'});
|
|||
|
|
htmx.ajax('GET', '/authorize/button/' + event.detail.name, {target: '#approve-btn', swap: 'outerHTML'});
|
|||
|
|
">
|
|||
|
|
|
|||
|
|
// Approve button with loading state
|
|||
|
|
<wa-button id="approve-btn" variant="brand"
|
|||
|
|
hx-post="/api/authorize/approve"
|
|||
|
|
hx-include="[name='scopes'],[name='wallet']"
|
|||
|
|
hx-target="#auth-card"
|
|||
|
|
hx-indicator="this">
|
|||
|
|
|
|||
|
|
// Wallet selector dropdown via HTMX
|
|||
|
|
<div class="wallet-selector"
|
|||
|
|
hx-get="/authorize/wallet-options"
|
|||
|
|
hx-target="#wallet-dropdown"
|
|||
|
|
hx-trigger="click">
|
|||
|
|
|
|||
|
|
// Out-of-band result update (closes modal/redirects)
|
|||
|
|
templ AuthResult(success bool, redirectURL string) {
|
|||
|
|
<div id="auth-card" hx-swap-oob="true">
|
|||
|
|
if success {
|
|||
|
|
@AuthResultSuccess()
|
|||
|
|
<script>setTimeout(() => window.location.href = "{ redirectURL }", 2000)</script>
|
|||
|
|
} else {
|
|||
|
|
@AuthResultDenied()
|
|||
|
|
}
|
|||
|
|
</div>
|
|||
|
|
}
|
|||
|
|
================================================================================
|
|||
|
|
-->
|
|||
|
|
<html lang="en" class="wa-cloak">
|
|||
|
|
<head>
|
|||
|
|
<meta charset="utf-8">
|
|||
|
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|||
|
|
<title>Allow Access - Sonr</title>
|
|||
|
|
<script src="https://cdn.sonr.org/wa/autoloader.js"></script>
|
|||
|
|
<style>
|
|||
|
|
:root {
|
|||
|
|
--wa-color-primary: #17c2ff;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
html, body {
|
|||
|
|
min-height: 100%;
|
|||
|
|
padding: 0;
|
|||
|
|
margin: 0;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.main-centered {
|
|||
|
|
display: flex;
|
|||
|
|
justify-content: center;
|
|||
|
|
align-items: center;
|
|||
|
|
min-height: 100%;
|
|||
|
|
padding: var(--wa-space-l);
|
|||
|
|
box-sizing: border-box;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.main-centered wa-card {
|
|||
|
|
width: 100%;
|
|||
|
|
max-width: 48ch;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/* App identity header */
|
|||
|
|
.app-identity {
|
|||
|
|
display: flex;
|
|||
|
|
flex-direction: column;
|
|||
|
|
align-items: center;
|
|||
|
|
gap: var(--wa-space-s);
|
|||
|
|
text-align: center;
|
|||
|
|
padding-bottom: var(--wa-space-m);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.app-logo {
|
|||
|
|
width: 64px;
|
|||
|
|
height: 64px;
|
|||
|
|
border-radius: var(--wa-radius-l);
|
|||
|
|
display: flex;
|
|||
|
|
align-items: center;
|
|||
|
|
justify-content: center;
|
|||
|
|
background: var(--wa-color-surface-alt);
|
|||
|
|
border: 1px solid var(--wa-color-neutral-200);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.app-logo img {
|
|||
|
|
width: 48px;
|
|||
|
|
height: 48px;
|
|||
|
|
border-radius: var(--wa-radius-m);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.app-name {
|
|||
|
|
display: flex;
|
|||
|
|
align-items: center;
|
|||
|
|
gap: var(--wa-space-2xs);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.verified-badge {
|
|||
|
|
color: var(--wa-color-primary);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/* Wallet selector */
|
|||
|
|
.wallet-selector {
|
|||
|
|
background: var(--wa-color-surface-alt);
|
|||
|
|
border-radius: var(--wa-radius-m);
|
|||
|
|
padding: var(--wa-space-s) var(--wa-space-m);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.wallet-address {
|
|||
|
|
font-family: var(--wa-font-mono);
|
|||
|
|
font-size: var(--wa-font-size-s);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/* Permission items */
|
|||
|
|
.permission-item {
|
|||
|
|
display: flex;
|
|||
|
|
align-items: flex-start;
|
|||
|
|
gap: var(--wa-space-s);
|
|||
|
|
padding: var(--wa-space-s) 0;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.permission-icon {
|
|||
|
|
flex-shrink: 0;
|
|||
|
|
width: 32px;
|
|||
|
|
height: 32px;
|
|||
|
|
border-radius: var(--wa-radius-s);
|
|||
|
|
display: flex;
|
|||
|
|
align-items: center;
|
|||
|
|
justify-content: center;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.permission-icon.read {
|
|||
|
|
background: var(--wa-color-success-subtle);
|
|||
|
|
color: var(--wa-color-success);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.permission-icon.write {
|
|||
|
|
background: var(--wa-color-warning-subtle);
|
|||
|
|
color: var(--wa-color-warning);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.permission-icon.sign {
|
|||
|
|
background: var(--wa-color-primary-subtle);
|
|||
|
|
color: var(--wa-color-primary);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.permission-icon.danger {
|
|||
|
|
background: var(--wa-color-danger-subtle);
|
|||
|
|
color: var(--wa-color-danger);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/* Transaction preview */
|
|||
|
|
.tx-preview {
|
|||
|
|
background: var(--wa-color-surface-alt);
|
|||
|
|
border-radius: var(--wa-radius-m);
|
|||
|
|
padding: var(--wa-space-m);
|
|||
|
|
font-family: var(--wa-font-mono);
|
|||
|
|
font-size: var(--wa-font-size-s);
|
|||
|
|
word-break: break-all;
|
|||
|
|
max-height: 120px;
|
|||
|
|
overflow-y: auto;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.tx-detail-row {
|
|||
|
|
display: flex;
|
|||
|
|
justify-content: space-between;
|
|||
|
|
padding: var(--wa-space-xs) 0;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.tx-detail-row:not(:last-child) {
|
|||
|
|
border-bottom: 1px solid var(--wa-color-neutral-200);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.tx-label {
|
|||
|
|
color: var(--wa-color-neutral-600);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.tx-value {
|
|||
|
|
font-family: var(--wa-font-mono);
|
|||
|
|
font-size: var(--wa-font-size-s);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/* Actions footer */
|
|||
|
|
.auth-actions {
|
|||
|
|
display: flex;
|
|||
|
|
gap: var(--wa-space-s);
|
|||
|
|
padding-top: var(--wa-space-m);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.auth-actions wa-button {
|
|||
|
|
flex: 1;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/* Tabs for different request types */
|
|||
|
|
.request-tabs wa-tab-panel {
|
|||
|
|
padding-top: var(--wa-space-m);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/* Raw data toggle */
|
|||
|
|
.raw-data-section {
|
|||
|
|
margin-top: var(--wa-space-m);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.raw-data-content {
|
|||
|
|
background: var(--wa-color-neutral-900);
|
|||
|
|
color: var(--wa-color-neutral-100);
|
|||
|
|
border-radius: var(--wa-radius-m);
|
|||
|
|
padding: var(--wa-space-m);
|
|||
|
|
font-family: var(--wa-font-mono);
|
|||
|
|
font-size: var(--wa-font-size-xs);
|
|||
|
|
word-break: break-all;
|
|||
|
|
max-height: 150px;
|
|||
|
|
overflow-y: auto;
|
|||
|
|
margin-top: var(--wa-space-s);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/* Viewport constraints for popup/webview (360-480px width) */
|
|||
|
|
@media (max-width: 400px) {
|
|||
|
|
.main-centered {
|
|||
|
|
padding: var(--wa-space-m);
|
|||
|
|
}
|
|||
|
|
.main-centered wa-card {
|
|||
|
|
max-width: 100%;
|
|||
|
|
}
|
|||
|
|
.app-logo {
|
|||
|
|
width: 48px;
|
|||
|
|
height: 48px;
|
|||
|
|
}
|
|||
|
|
.app-logo img {
|
|||
|
|
width: 36px;
|
|||
|
|
height: 36px;
|
|||
|
|
}
|
|||
|
|
.permission-icon {
|
|||
|
|
width: 28px;
|
|||
|
|
height: 28px;
|
|||
|
|
}
|
|||
|
|
.wallet-selector {
|
|||
|
|
padding: var(--wa-space-xs) var(--wa-space-s);
|
|||
|
|
}
|
|||
|
|
.tx-preview {
|
|||
|
|
padding: var(--wa-space-s);
|
|||
|
|
max-height: 100px;
|
|||
|
|
}
|
|||
|
|
.raw-data-content {
|
|||
|
|
padding: var(--wa-space-s);
|
|||
|
|
max-height: 100px;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
@media (max-height: 600px) {
|
|||
|
|
.app-identity {
|
|||
|
|
padding-bottom: var(--wa-space-s);
|
|||
|
|
}
|
|||
|
|
.app-logo {
|
|||
|
|
width: 48px;
|
|||
|
|
height: 48px;
|
|||
|
|
}
|
|||
|
|
.permission-item {
|
|||
|
|
padding: var(--wa-space-xs) 0;
|
|||
|
|
}
|
|||
|
|
.tx-preview {
|
|||
|
|
max-height: 80px;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
</style>
|
|||
|
|
</head>
|
|||
|
|
<body>
|
|||
|
|
<wa-page>
|
|||
|
|
<main class="main-centered">
|
|||
|
|
<wa-card>
|
|||
|
|
<!-- App Identity Header -->
|
|||
|
|
<div class="app-identity">
|
|||
|
|
<div class="app-logo">
|
|||
|
|
<wa-icon name="cube" style="font-size: 32px; color: var(--wa-color-primary);"></wa-icon>
|
|||
|
|
</div>
|
|||
|
|
<div class="wa-stack wa-gap-2xs" style="align-items: center;">
|
|||
|
|
<div class="app-name">
|
|||
|
|
<span class="wa-heading-l">Uniswap</span>
|
|||
|
|
<wa-icon name="badge-check" variant="solid" class="verified-badge"></wa-icon>
|
|||
|
|
</div>
|
|||
|
|
<span class="wa-caption-s" style="color: var(--wa-color-neutral-500);">app.uniswap.org</span>
|
|||
|
|
</div>
|
|||
|
|
</div>
|
|||
|
|
|
|||
|
|
<wa-divider></wa-divider>
|
|||
|
|
|
|||
|
|
<!-- Request Type Tabs -->
|
|||
|
|
<wa-tab-group class="request-tabs">
|
|||
|
|
<wa-tab panel="connect">
|
|||
|
|
<div class="wa-cluster wa-gap-xs">
|
|||
|
|
<wa-icon name="link"></wa-icon>
|
|||
|
|
<span>Connect</span>
|
|||
|
|
</div>
|
|||
|
|
</wa-tab>
|
|||
|
|
<wa-tab panel="sign">
|
|||
|
|
<div class="wa-cluster wa-gap-xs">
|
|||
|
|
<wa-icon name="pen-nib"></wa-icon>
|
|||
|
|
<span>Sign</span>
|
|||
|
|
</div>
|
|||
|
|
</wa-tab>
|
|||
|
|
<wa-tab panel="transaction">
|
|||
|
|
<div class="wa-cluster wa-gap-xs">
|
|||
|
|
<wa-icon name="paper-plane"></wa-icon>
|
|||
|
|
<span>Transaction</span>
|
|||
|
|
</div>
|
|||
|
|
</wa-tab>
|
|||
|
|
|
|||
|
|
<!-- Connect Panel -->
|
|||
|
|
<wa-tab-panel name="connect">
|
|||
|
|
<div class="wa-stack">
|
|||
|
|
<!-- Wallet Selection -->
|
|||
|
|
<div class="wallet-selector">
|
|||
|
|
<div class="wa-flank">
|
|||
|
|
<div class="wa-cluster wa-gap-s">
|
|||
|
|
<wa-avatar initials="S" style="--size: 36px;"></wa-avatar>
|
|||
|
|
<div class="wa-stack wa-gap-0">
|
|||
|
|
<span class="wa-heading-s">Main Wallet</span>
|
|||
|
|
<span class="wallet-address">sonr1x9f...7k2m</span>
|
|||
|
|
</div>
|
|||
|
|
</div>
|
|||
|
|
<wa-icon-button name="chevron-down" variant="plain"></wa-icon-button>
|
|||
|
|
</div>
|
|||
|
|
</div>
|
|||
|
|
|
|||
|
|
<!-- Permissions List -->
|
|||
|
|
<div class="wa-stack wa-gap-xs">
|
|||
|
|
<span class="wa-heading-s">This app wants to:</span>
|
|||
|
|
|
|||
|
|
<div class="permission-item">
|
|||
|
|
<div class="permission-icon read">
|
|||
|
|
<wa-icon name="eye"></wa-icon>
|
|||
|
|
</div>
|
|||
|
|
<div class="wa-stack wa-gap-0">
|
|||
|
|
<span class="wa-heading-xs">See your wallet address</span>
|
|||
|
|
<span class="wa-caption-s" style="color: var(--wa-color-neutral-500);">Your public address only</span>
|
|||
|
|
</div>
|
|||
|
|
</div>
|
|||
|
|
|
|||
|
|
<div class="permission-item">
|
|||
|
|
<div class="permission-icon read">
|
|||
|
|
<wa-icon name="coins"></wa-icon>
|
|||
|
|
</div>
|
|||
|
|
<div class="wa-stack wa-gap-0">
|
|||
|
|
<span class="wa-heading-xs">See your balances</span>
|
|||
|
|
<span class="wa-caption-s" style="color: var(--wa-color-neutral-500);">Token amounts in your wallet</span>
|
|||
|
|
</div>
|
|||
|
|
</div>
|
|||
|
|
|
|||
|
|
<div class="permission-item">
|
|||
|
|
<div class="permission-icon write">
|
|||
|
|
<wa-icon name="bell"></wa-icon>
|
|||
|
|
</div>
|
|||
|
|
<div class="wa-stack wa-gap-0">
|
|||
|
|
<span class="wa-heading-xs">Ask to send transactions</span>
|
|||
|
|
<span class="wa-caption-s" style="color: var(--wa-color-neutral-500);">You'll approve each one separately</span>
|
|||
|
|
</div>
|
|||
|
|
</div>
|
|||
|
|
</div>
|
|||
|
|
|
|||
|
|
<!-- Warning Callout -->
|
|||
|
|
<wa-callout variant="neutral">
|
|||
|
|
<wa-icon slot="icon" name="shield-check"></wa-icon>
|
|||
|
|
<div class="wa-stack wa-gap-2xs">
|
|||
|
|
<span class="wa-heading-xs">This app cannot:</span>
|
|||
|
|
<span class="wa-caption-s">Spend your funds without asking you first.</span>
|
|||
|
|
</div>
|
|||
|
|
</wa-callout>
|
|||
|
|
</div>
|
|||
|
|
</wa-tab-panel>
|
|||
|
|
|
|||
|
|
<!-- Sign Message Panel -->
|
|||
|
|
<wa-tab-panel name="sign">
|
|||
|
|
<div class="wa-stack">
|
|||
|
|
<!-- Signing Wallet -->
|
|||
|
|
<div class="wallet-selector">
|
|||
|
|
<div class="wa-flank">
|
|||
|
|
<div class="wa-cluster wa-gap-s">
|
|||
|
|
<wa-avatar initials="S" style="--size: 36px;"></wa-avatar>
|
|||
|
|
<div class="wa-stack wa-gap-0">
|
|||
|
|
<span class="wa-heading-s">Main Wallet</span>
|
|||
|
|
<span class="wallet-address">sonr1x9f...7k2m</span>
|
|||
|
|
</div>
|
|||
|
|
</div>
|
|||
|
|
<wa-badge variant="neutral">Signing</wa-badge>
|
|||
|
|
</div>
|
|||
|
|
</div>
|
|||
|
|
|
|||
|
|
<!-- Message Preview -->
|
|||
|
|
<div class="wa-stack wa-gap-xs">
|
|||
|
|
<span class="wa-heading-s">You're signing this message:</span>
|
|||
|
|
<div class="tx-preview">
|
|||
|
|
Welcome to Uniswap!
|
|||
|
|
|
|||
|
|
Click to sign in and accept the Uniswap Terms of Service.
|
|||
|
|
|
|||
|
|
This request will not trigger a blockchain transaction or cost any gas fees.
|
|||
|
|
|
|||
|
|
Wallet address:
|
|||
|
|
sonr1x9f4h2k8m3n5p7q2r4s6t8v0w3x5y7z9a1b3c5d7k2m
|
|||
|
|
|
|||
|
|
Nonce: 8f4a2b1c
|
|||
|
|
</div>
|
|||
|
|
</div>
|
|||
|
|
|
|||
|
|
<!-- Sign Type Info -->
|
|||
|
|
<wa-callout variant="brand" appearance="filled">
|
|||
|
|
<wa-icon slot="icon" name="signature"></wa-icon>
|
|||
|
|
<div class="wa-stack wa-gap-2xs">
|
|||
|
|
<span class="wa-heading-xs">Free to sign</span>
|
|||
|
|
<span class="wa-caption-s">No fees. This just proves you own this wallet.</span>
|
|||
|
|
</div>
|
|||
|
|
</wa-callout>
|
|||
|
|
|
|||
|
|
<!-- Advanced: Raw Data Toggle -->
|
|||
|
|
<div class="raw-data-section">
|
|||
|
|
<wa-details summary="Show technical details">
|
|||
|
|
<div class="raw-data-content">
|
|||
|
|
0x57656c636f6d6520746f20556e697377617021
|
|||
|
|
|
|||
|
|
436c69636b20746f207369676e20696e20616e642061636365707420746865
|
|||
|
|
20556e6973776170205465726d73206f6620536572766963652e0a0a5468
|
|||
|
|
69732072657175657374200a0a57616c6c65742061646472657373
|
|||
|
|
</div>
|
|||
|
|
</wa-details>
|
|||
|
|
</div>
|
|||
|
|
</div>
|
|||
|
|
</wa-tab-panel>
|
|||
|
|
|
|||
|
|
<!-- Transaction Panel -->
|
|||
|
|
<wa-tab-panel name="transaction">
|
|||
|
|
<div class="wa-stack">
|
|||
|
|
<!-- From Wallet -->
|
|||
|
|
<div class="wallet-selector">
|
|||
|
|
<div class="wa-flank">
|
|||
|
|
<div class="wa-cluster wa-gap-s">
|
|||
|
|
<wa-avatar initials="S" style="--size: 36px;"></wa-avatar>
|
|||
|
|
<div class="wa-stack wa-gap-0">
|
|||
|
|
<span class="wa-heading-s">Main Wallet</span>
|
|||
|
|
<span class="wallet-address">sonr1x9f...7k2m</span>
|
|||
|
|
</div>
|
|||
|
|
</div>
|
|||
|
|
<div class="wa-stack wa-gap-0" style="text-align: right;">
|
|||
|
|
<span class="wa-caption-s" style="color: var(--wa-color-neutral-500);">Balance</span>
|
|||
|
|
<span class="wa-heading-s">1,234.56 SNR</span>
|
|||
|
|
</div>
|
|||
|
|
</div>
|
|||
|
|
</div>
|
|||
|
|
|
|||
|
|
<!-- Transaction Details Card -->
|
|||
|
|
<wa-card>
|
|||
|
|
<div class="wa-stack wa-gap-s">
|
|||
|
|
<div class="wa-flank">
|
|||
|
|
<span class="wa-heading-m">Swap</span>
|
|||
|
|
<wa-badge variant="warning">Waiting</wa-badge>
|
|||
|
|
</div>
|
|||
|
|
|
|||
|
|
<wa-divider></wa-divider>
|
|||
|
|
|
|||
|
|
<!-- Swap Visual -->
|
|||
|
|
<div class="wa-stack wa-gap-s">
|
|||
|
|
<div class="wa-flank">
|
|||
|
|
<div class="wa-cluster wa-gap-s">
|
|||
|
|
<wa-avatar initials="E" style="--size: 32px; background: var(--wa-color-neutral-200);"></wa-avatar>
|
|||
|
|
<div class="wa-stack wa-gap-0">
|
|||
|
|
<span class="wa-heading-s">100.00 ETH</span>
|
|||
|
|
<span class="wa-caption-s" style="color: var(--wa-color-neutral-500);">Ethereum</span>
|
|||
|
|
</div>
|
|||
|
|
</div>
|
|||
|
|
<span class="wa-caption-s" style="color: var(--wa-color-danger);">-$234,567.00</span>
|
|||
|
|
</div>
|
|||
|
|
|
|||
|
|
<div style="text-align: center;">
|
|||
|
|
<wa-icon name="arrow-down" style="color: var(--wa-color-neutral-400);"></wa-icon>
|
|||
|
|
</div>
|
|||
|
|
|
|||
|
|
<div class="wa-flank">
|
|||
|
|
<div class="wa-cluster wa-gap-s">
|
|||
|
|
<wa-avatar initials="U" style="--size: 32px; background: var(--wa-color-primary-subtle);"></wa-avatar>
|
|||
|
|
<div class="wa-stack wa-gap-0">
|
|||
|
|
<span class="wa-heading-s">~125,000 USDC</span>
|
|||
|
|
<span class="wa-caption-s" style="color: var(--wa-color-neutral-500);">USD Coin</span>
|
|||
|
|
</div>
|
|||
|
|
</div>
|
|||
|
|
<span class="wa-caption-s" style="color: var(--wa-color-success);">+$125,000.00</span>
|
|||
|
|
</div>
|
|||
|
|
</div>
|
|||
|
|
</div>
|
|||
|
|
</wa-card>
|
|||
|
|
|
|||
|
|
<!-- Gas & Fee Details -->
|
|||
|
|
<div class="wa-stack wa-gap-2xs">
|
|||
|
|
<div class="tx-detail-row">
|
|||
|
|
<span class="tx-label">Network</span>
|
|||
|
|
<span class="tx-value">Sonr Mainnet</span>
|
|||
|
|
</div>
|
|||
|
|
<div class="tx-detail-row">
|
|||
|
|
<span class="tx-label">Network fee</span>
|
|||
|
|
<span class="tx-value">~$0.12</span>
|
|||
|
|
</div>
|
|||
|
|
<div class="tx-detail-row">
|
|||
|
|
<span class="tx-label">Max fee</span>
|
|||
|
|
<span class="tx-value">$0.26</span>
|
|||
|
|
</div>
|
|||
|
|
<div class="tx-detail-row">
|
|||
|
|
<span class="tx-label">Price change limit</span>
|
|||
|
|
<span class="tx-value">0.5%</span>
|
|||
|
|
</div>
|
|||
|
|
</div>
|
|||
|
|
|
|||
|
|
<!-- Danger Warning for High Value -->
|
|||
|
|
<wa-callout variant="warning">
|
|||
|
|
<wa-icon slot="icon" name="triangle-alert"></wa-icon>
|
|||
|
|
<div class="wa-stack wa-gap-2xs">
|
|||
|
|
<span class="wa-heading-xs">Large amount</span>
|
|||
|
|
<span class="wa-caption-s">Please review the details carefully before confirming.</span>
|
|||
|
|
</div>
|
|||
|
|
</wa-callout>
|
|||
|
|
|
|||
|
|
<!-- Advanced: Contract Data -->
|
|||
|
|
<div class="raw-data-section">
|
|||
|
|
<wa-details summary="Show technical details">
|
|||
|
|
<div class="wa-stack wa-gap-s">
|
|||
|
|
<div class="tx-detail-row">
|
|||
|
|
<span class="tx-label">Contract</span>
|
|||
|
|
<span class="tx-value">0x7a25...3f8b</span>
|
|||
|
|
</div>
|
|||
|
|
<div class="tx-detail-row">
|
|||
|
|
<span class="tx-label">Function</span>
|
|||
|
|
<span class="tx-value">swapExactTokensForTokens()</span>
|
|||
|
|
</div>
|
|||
|
|
<div class="raw-data-content">
|
|||
|
|
0x38ed1739
|
|||
|
|
0000000000000000000000000000000000000056bc75e2d63100000
|
|||
|
|
000000000000000000000000000000000000001e8480
|
|||
|
|
00000000000000000000000000000000000000000000000000000060
|
|||
|
|
0000000000000000000000007a250d5630b4cf539739df2c5dacb4c659
|
|||
|
|
</div>
|
|||
|
|
</div>
|
|||
|
|
</wa-details>
|
|||
|
|
</div>
|
|||
|
|
</div>
|
|||
|
|
</wa-tab-panel>
|
|||
|
|
</wa-tab-group>
|
|||
|
|
|
|||
|
|
<!-- Footer Actions -->
|
|||
|
|
<div slot="footer">
|
|||
|
|
<div class="wa-stack wa-gap-s">
|
|||
|
|
<div class="auth-actions">
|
|||
|
|
<wa-button variant="neutral" appearance="outlined" id="deny-btn">
|
|||
|
|
<wa-icon slot="start" name="x"></wa-icon>
|
|||
|
|
Cancel
|
|||
|
|
</wa-button>
|
|||
|
|
<wa-button variant="brand" id="approve-btn">
|
|||
|
|
<wa-icon slot="start" name="check"></wa-icon>
|
|||
|
|
Allow
|
|||
|
|
</wa-button>
|
|||
|
|
</div>
|
|||
|
|
<div style="text-align: center;">
|
|||
|
|
<span class="wa-caption-s" style="color: var(--wa-color-neutral-500);">
|
|||
|
|
<wa-icon name="lock" style="font-size: 12px;"></wa-icon>
|
|||
|
|
Protected by Sonr
|
|||
|
|
</span>
|
|||
|
|
</div>
|
|||
|
|
</div>
|
|||
|
|
</div>
|
|||
|
|
</wa-card>
|
|||
|
|
</main>
|
|||
|
|
</wa-page>
|
|||
|
|
|
|||
|
|
<script>
|
|||
|
|
// Tab panel state management
|
|||
|
|
const tabGroup = document.querySelector('wa-tab-group');
|
|||
|
|
const approveBtn = document.getElementById('approve-btn');
|
|||
|
|
const denyBtn = document.getElementById('deny-btn');
|
|||
|
|
|
|||
|
|
// Update button text based on active panel
|
|||
|
|
tabGroup.addEventListener('wa-tab-show', (event) => {
|
|||
|
|
const panel = event.detail.name;
|
|||
|
|
|
|||
|
|
switch (panel) {
|
|||
|
|
case 'connect':
|
|||
|
|
approveBtn.innerHTML = '<wa-icon slot="start" name="link"></wa-icon>Allow';
|
|||
|
|
break;
|
|||
|
|
case 'sign':
|
|||
|
|
approveBtn.innerHTML = '<wa-icon slot="start" name="pen-nib"></wa-icon>Sign';
|
|||
|
|
break;
|
|||
|
|
case 'transaction':
|
|||
|
|
approveBtn.innerHTML = '<wa-icon slot="start" name="paper-plane"></wa-icon>Confirm';
|
|||
|
|
break;
|
|||
|
|
}
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
// Simulated approve action
|
|||
|
|
approveBtn.addEventListener('click', async () => {
|
|||
|
|
const activePanel = tabGroup.querySelector('wa-tab[active]')?.getAttribute('panel') || 'connect';
|
|||
|
|
|
|||
|
|
approveBtn.loading = true;
|
|||
|
|
approveBtn.disabled = true;
|
|||
|
|
denyBtn.disabled = true;
|
|||
|
|
|
|||
|
|
// Simulate WebAuthn authentication
|
|||
|
|
await new Promise(resolve => setTimeout(resolve, 1500));
|
|||
|
|
|
|||
|
|
// Show success
|
|||
|
|
const card = document.querySelector('wa-card');
|
|||
|
|
card.innerHTML = `
|
|||
|
|
<div class="wa-stack" style="text-align: center; padding: var(--wa-space-xl) 0;">
|
|||
|
|
<wa-icon name="circle-check" variant="solid" style="font-size: 64px; color: var(--wa-color-success);"></wa-icon>
|
|||
|
|
<span class="wa-heading-l">${activePanel === 'connect' ? 'Connected' : activePanel === 'sign' ? 'Signed' : 'Sent'}</span>
|
|||
|
|
<span class="wa-caption-m" style="color: var(--wa-color-neutral-500);">
|
|||
|
|
${activePanel === 'transaction' ? 'Transaction ID: 0x8f4a2b1c...' : 'You can close this window.'}
|
|||
|
|
</span>
|
|||
|
|
<wa-button variant="neutral" appearance="outlined" onclick="window.close()">
|
|||
|
|
Close
|
|||
|
|
</wa-button>
|
|||
|
|
</div>
|
|||
|
|
`;
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
// Deny action
|
|||
|
|
denyBtn.addEventListener('click', () => {
|
|||
|
|
const card = document.querySelector('wa-card');
|
|||
|
|
card.innerHTML = `
|
|||
|
|
<div class="wa-stack" style="text-align: center; padding: var(--wa-space-xl) 0;">
|
|||
|
|
<wa-icon name="circle-x" variant="solid" style="font-size: 64px; color: var(--wa-color-danger);"></wa-icon>
|
|||
|
|
<span class="wa-heading-l">Cancelled</span>
|
|||
|
|
<span class="wa-caption-m" style="color: var(--wa-color-neutral-500);">
|
|||
|
|
No access was granted.
|
|||
|
|
</span>
|
|||
|
|
<wa-button variant="neutral" appearance="outlined" onclick="window.close()">
|
|||
|
|
Close
|
|||
|
|
</wa-button>
|
|||
|
|
</div>
|
|||
|
|
`;
|
|||
|
|
});
|
|||
|
|
</script>
|
|||
|
|
</body>
|
|||
|
|
</html>
|