Compare commits
No commits in common. "985c1494b59a6474f4705b2c83a4c0da94a145f8" and "65eda5648cf50be6c86afcaa12276df1bad9888c" have entirely different histories.
985c1494b5
...
65eda5648c
18 changed files with 176 additions and 818 deletions
|
@ -1,12 +1,8 @@
|
||||||
import { wallet } from '@/wallet';
|
|
||||||
import { StateController } from '@lit-app/state';
|
|
||||||
import type { NDKUser } from '@nostr-dev-kit/ndk';
|
|
||||||
import formatDateTime from '@utils/formatDateTime';
|
import formatDateTime from '@utils/formatDateTime';
|
||||||
import { LitElement, css, html } from 'lit';
|
import { LitElement, css, html } from 'lit';
|
||||||
import { customElement, property, state } from 'lit/decorators.js';
|
import { customElement, property } from 'lit/decorators.js';
|
||||||
import { classMap } from 'lit/directives/class-map.js';
|
import { classMap } from 'lit/directives/class-map.js';
|
||||||
|
|
||||||
import { ndk } from '@/ndk';
|
|
||||||
import '@components/MarkdownContent';
|
import '@components/MarkdownContent';
|
||||||
|
|
||||||
@customElement('arx-forum-post')
|
@customElement('arx-forum-post')
|
||||||
|
@ -18,16 +14,6 @@ export class ForumPost extends LitElement {
|
||||||
@property({ type: String }) content = '';
|
@property({ type: String }) content = '';
|
||||||
@property({ type: Boolean }) isHighlighted = false;
|
@property({ type: Boolean }) isHighlighted = false;
|
||||||
|
|
||||||
@state() private zapAmountDialogOpen = false;
|
|
||||||
@state() public authorProfile: NDKUser | undefined = undefined;
|
|
||||||
|
|
||||||
override connectedCallback(): void {
|
|
||||||
super.connectedCallback();
|
|
||||||
this.authorProfile = ndk.getUser({ pubkey: this.npub });
|
|
||||||
new StateController(this, wallet);
|
|
||||||
wallet.loadWallet();
|
|
||||||
}
|
|
||||||
|
|
||||||
static override styles = [
|
static override styles = [
|
||||||
css`
|
css`
|
||||||
.post {
|
.post {
|
||||||
|
@ -185,24 +171,14 @@ export class ForumPost extends LitElement {
|
||||||
}
|
}
|
||||||
|
|
||||||
private _handleZap() {
|
private _handleZap() {
|
||||||
// setting to false and then to true forces the dialog to open, even when it wasn't closed correctly
|
alert('Zapping is not yet implemented');
|
||||||
this.zapAmountDialogOpen = false;
|
this.dispatchEvent(
|
||||||
setTimeout(() => {
|
new CustomEvent('zap', {
|
||||||
this.zapAmountDialogOpen = true;
|
detail: { postId: this.id, npub: this.npub },
|
||||||
}, 0);
|
bubbles: true,
|
||||||
}
|
composed: true,
|
||||||
|
}),
|
||||||
private async _doZap(e: Event) {
|
);
|
||||||
if (!(e instanceof CustomEvent)) return;
|
|
||||||
e.preventDefault();
|
|
||||||
const zapAmount = Number.parseInt(e.detail.value);
|
|
||||||
if (Number.isNaN(zapAmount) || zapAmount <= 10) {
|
|
||||||
alert('Zap amount must be greater or equal to 10');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
await wallet.nutZap(zapAmount, this.authorProfile!, this.id);
|
|
||||||
|
|
||||||
this.zapAmountDialogOpen = false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private _handleDownzap() {
|
private _handleDownzap() {
|
||||||
|
@ -230,16 +206,6 @@ export class ForumPost extends LitElement {
|
||||||
};
|
};
|
||||||
|
|
||||||
return html`
|
return html`
|
||||||
<arx-prompt
|
|
||||||
promptText="Zap amount"
|
|
||||||
placeholder="Enter zap amount"
|
|
||||||
@save=${this._doZap}
|
|
||||||
@cancel=${() => {
|
|
||||||
this.zapAmountDialogOpen = false;
|
|
||||||
}}
|
|
||||||
showInput
|
|
||||||
.open=${this.zapAmountDialogOpen}
|
|
||||||
></arx-prompt>
|
|
||||||
<div class=${classMap(postClasses)} id="post-${this.id}">
|
<div class=${classMap(postClasses)} id="post-${this.id}">
|
||||||
<div class="post__sidebar">
|
<div class="post__sidebar">
|
||||||
<arx-nostr-profile
|
<arx-nostr-profile
|
||||||
|
@ -282,7 +248,7 @@ export class ForumPost extends LitElement {
|
||||||
></iconify-icon>
|
></iconify-icon>
|
||||||
</arx-button>
|
</arx-button>
|
||||||
|
|
||||||
<arx-button label="Zap" @click=${this._handleZap}>
|
<arx-button label="Zap" @click=${this._handleZap} disabled>
|
||||||
<iconify-icon
|
<iconify-icon
|
||||||
slot="prefix"
|
slot="prefix"
|
||||||
icon="mdi:lightning-bolt"
|
icon="mdi:lightning-bolt"
|
||||||
|
|
|
@ -1,10 +1,9 @@
|
||||||
import { StyledToggle } from '@/components/General/Toggle';
|
import { StyledToggle } from '@/components/General/Toggle';
|
||||||
import { ArxInputChangeEvent, type StyledInput } from '@components/General/Input';
|
|
||||||
import { LitElement, html } from 'lit';
|
import { LitElement, html } from 'lit';
|
||||||
import { customElement, state } from 'lit/decorators.js';
|
import { customElement, state } from 'lit/decorators.js';
|
||||||
|
|
||||||
import '@components/General/Fieldset';
|
|
||||||
import '@components/General/Input';
|
import '@components/General/Input';
|
||||||
|
import '@components/General/Fieldset';
|
||||||
import '@components/General/Select';
|
import '@components/General/Select';
|
||||||
|
|
||||||
interface DateTimeFormatOptions {
|
interface DateTimeFormatOptions {
|
||||||
|
@ -72,10 +71,10 @@ export class DateTimeSettings extends LitElement {
|
||||||
}
|
}
|
||||||
|
|
||||||
private handleChange(key: keyof DateTimeFormatOptions, e: Event) {
|
private handleChange(key: keyof DateTimeFormatOptions, e: Event) {
|
||||||
let value = e instanceof ArxInputChangeEvent ? e.detail.value : (e.target as HTMLSelectElement).value;
|
const target = e.target as HTMLSelectElement | HTMLInputElement | StyledToggle;
|
||||||
const target = e.target as StyledInput | HTMLSelectElement | HTMLInputElement | StyledToggle;
|
let value: string | boolean | undefined = target.value;
|
||||||
|
|
||||||
if (key === 'hour12' && target instanceof StyledToggle) value = target.checked.toString();
|
if (key === 'hour12' && target instanceof StyledToggle) value = target.checked;
|
||||||
|
|
||||||
this.options = {
|
this.options = {
|
||||||
...this.options,
|
...this.options,
|
||||||
|
@ -127,7 +126,7 @@ export class DateTimeSettings extends LitElement {
|
||||||
label="Locale"
|
label="Locale"
|
||||||
type="text"
|
type="text"
|
||||||
.value=${this.options.locale}
|
.value=${this.options.locale}
|
||||||
@change=${(e: Event) => this.handleChange('locale', e)}
|
@input=${(e: Event) => this.handleChange('locale', e)}
|
||||||
>
|
>
|
||||||
</arx-input>
|
</arx-input>
|
||||||
|
|
||||||
|
|
|
@ -186,8 +186,8 @@ export class StyledButton extends LitElement {
|
||||||
}
|
}
|
||||||
|
|
||||||
private _handleClick(e: MouseEvent) {
|
private _handleClick(e: MouseEvent) {
|
||||||
e.preventDefault();
|
|
||||||
if (this.disabled || this.loading) {
|
if (this.disabled || this.loading) {
|
||||||
|
e.preventDefault();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,35 +1,17 @@
|
||||||
import { LitElement, type PropertyValues, css, html } from 'lit';
|
import { LitElement, css, html } from 'lit';
|
||||||
import { customElement, property, query, state } from 'lit/decorators.js';
|
import { customElement, property } from 'lit/decorators.js';
|
||||||
import { when } from 'lit/directives/when.js';
|
import { when } from 'lit/directives/when.js';
|
||||||
|
|
||||||
export class ArxInputChangeEvent extends CustomEvent<{ value: string }> {
|
|
||||||
constructor(value: string) {
|
|
||||||
super('change', { detail: { value } });
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@customElement('arx-input')
|
@customElement('arx-input')
|
||||||
export class StyledInput extends LitElement {
|
export class StyledInput extends LitElement {
|
||||||
@property() placeholder = '';
|
@property() placeholder = '';
|
||||||
@property() value = '';
|
@property() value = '';
|
||||||
@property({ type: Boolean }) disabled = false;
|
@property({ type: Boolean }) disabled = false;
|
||||||
@property() type: 'text' | 'number' | 'password' = 'text';
|
@property() type = 'text';
|
||||||
@property() name = '';
|
@property() name = '';
|
||||||
@property({ type: Boolean }) required = false;
|
@property({ type: Boolean }) required = false;
|
||||||
@property() label = '';
|
@property() label = '';
|
||||||
|
|
||||||
@query('input') private _input!: HTMLInputElement;
|
|
||||||
|
|
||||||
@state() private _value = '';
|
|
||||||
|
|
||||||
protected override firstUpdated(_changedProperties: PropertyValues): void {
|
|
||||||
this._value = this.value;
|
|
||||||
}
|
|
||||||
|
|
||||||
protected override updated(changedProperties: PropertyValues): void {
|
|
||||||
if (changedProperties.has('value')) this._value = this.value;
|
|
||||||
}
|
|
||||||
|
|
||||||
static override styles = css`
|
static override styles = css`
|
||||||
:host {
|
:host {
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
|
@ -116,7 +98,7 @@ export class StyledInput extends LitElement {
|
||||||
return html`
|
return html`
|
||||||
${when(this.label, () => html`<label for="input-${this.name}">${this.label}</label>`)}
|
${when(this.label, () => html`<label for="input-${this.name}">${this.label}</label>`)}
|
||||||
<input
|
<input
|
||||||
.value=${this._value}
|
.value=${this.value}
|
||||||
?disabled=${this.disabled}
|
?disabled=${this.disabled}
|
||||||
?required=${this.required}
|
?required=${this.required}
|
||||||
placeholder=${this.placeholder}
|
placeholder=${this.placeholder}
|
||||||
|
@ -132,24 +114,52 @@ export class StyledInput extends LitElement {
|
||||||
}
|
}
|
||||||
|
|
||||||
private _handleKeyDown(e: KeyboardEvent) {
|
private _handleKeyDown(e: KeyboardEvent) {
|
||||||
this.dispatchEvent(new CustomEvent('keydown', { detail: { key: e.key }, composed: true, bubbles: true }));
|
this.dispatchEvent(
|
||||||
|
new CustomEvent('keydown', {
|
||||||
|
detail: { key: e.key },
|
||||||
|
bubbles: true,
|
||||||
|
composed: true,
|
||||||
|
}),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
private _handleKeyUp(e: KeyboardEvent) {
|
private _handleKeyUp(e: KeyboardEvent) {
|
||||||
this.dispatchEvent(new CustomEvent('keyup', { detail: { key: e.key }, composed: true, bubbles: true }));
|
this.dispatchEvent(
|
||||||
|
new CustomEvent('keyup', {
|
||||||
|
detail: { key: e.key },
|
||||||
|
bubbles: true,
|
||||||
|
composed: true,
|
||||||
|
}),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
private _handleFocus() {
|
private _handleFocus() {
|
||||||
this.dispatchEvent(new CustomEvent('focus', { composed: true, bubbles: true }));
|
this.dispatchEvent(
|
||||||
|
new CustomEvent('focus', {
|
||||||
|
bubbles: true,
|
||||||
|
composed: true,
|
||||||
|
}),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
private _handleBlur() {
|
private _handleBlur() {
|
||||||
this.dispatchEvent(new CustomEvent('blur', { composed: true, bubbles: true }));
|
this.dispatchEvent(
|
||||||
|
new CustomEvent('blur', {
|
||||||
|
bubbles: true,
|
||||||
|
composed: true,
|
||||||
|
}),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
private _handleInput(e: InputEvent) {
|
private _handleInput(e: InputEvent) {
|
||||||
this._value = this._input.value;
|
const input = e.target as HTMLInputElement;
|
||||||
this.dispatchEvent(new ArxInputChangeEvent(this._value));
|
this.value = input.value;
|
||||||
e.preventDefault();
|
this.dispatchEvent(
|
||||||
|
new CustomEvent('input', {
|
||||||
|
detail: { value: this.value },
|
||||||
|
bubbles: true,
|
||||||
|
composed: true,
|
||||||
|
}),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,4 +1,3 @@
|
||||||
import type { ArxInputChangeEvent } from '@components/General/Input';
|
|
||||||
import { LitElement, css, html } from 'lit';
|
import { LitElement, css, html } from 'lit';
|
||||||
import { customElement, property, state } from 'lit/decorators.js';
|
import { customElement, property, state } from 'lit/decorators.js';
|
||||||
import { classMap } from 'lit/directives/class-map.js';
|
import { classMap } from 'lit/directives/class-map.js';
|
||||||
|
@ -132,8 +131,9 @@ export class EvePrompt extends LitElement {
|
||||||
if (e.key === 'Enter' && !e.shiftKey) this._handleSave();
|
if (e.key === 'Enter' && !e.shiftKey) this._handleSave();
|
||||||
}
|
}
|
||||||
|
|
||||||
private _handleInputChange(e: ArxInputChangeEvent) {
|
private _handleInputChange(e: Event) {
|
||||||
this._inputValue = e.detail.value;
|
const target = e.target as HTMLInputElement;
|
||||||
|
this._inputValue = target.value;
|
||||||
}
|
}
|
||||||
|
|
||||||
private _handleCancel() {
|
private _handleCancel() {
|
||||||
|
@ -166,7 +166,7 @@ export class EvePrompt extends LitElement {
|
||||||
type="text"
|
type="text"
|
||||||
class="input-field"
|
class="input-field"
|
||||||
.value=${this._inputValue}
|
.value=${this._inputValue}
|
||||||
@change=${this._handleInputChange}
|
@input=${this._handleInputChange}
|
||||||
placeholder=${this.placeholder}
|
placeholder=${this.placeholder}
|
||||||
></arx-input>
|
></arx-input>
|
||||||
`
|
`
|
||||||
|
|
|
@ -1,7 +1,6 @@
|
||||||
import { LitElement, css, html } from 'lit';
|
import { LitElement, css, html } from 'lit';
|
||||||
import { customElement, property } from 'lit/decorators.js';
|
import { customElement, property } from 'lit/decorators.js';
|
||||||
import { when } from 'lit/directives/when.js';
|
import { when } from 'lit/directives/when.js';
|
||||||
import { ArxInputChangeEvent } from './Input';
|
|
||||||
|
|
||||||
@customElement('arx-textarea')
|
@customElement('arx-textarea')
|
||||||
export class StyledTextarea extends LitElement {
|
export class StyledTextarea extends LitElement {
|
||||||
|
@ -202,6 +201,12 @@ export class StyledTextarea extends LitElement {
|
||||||
private _handleInput(e: InputEvent) {
|
private _handleInput(e: InputEvent) {
|
||||||
const textarea = e.target as HTMLTextAreaElement;
|
const textarea = e.target as HTMLTextAreaElement;
|
||||||
this.value = textarea.value;
|
this.value = textarea.value;
|
||||||
this.dispatchEvent(new ArxInputChangeEvent(this.value));
|
this.dispatchEvent(
|
||||||
|
new CustomEvent('input', {
|
||||||
|
detail: { value: this.value },
|
||||||
|
bubbles: true,
|
||||||
|
composed: true,
|
||||||
|
}),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,4 @@
|
||||||
import { ndk } from '@/ndk';
|
import { ndk } from '@/ndk';
|
||||||
import type { ArxInputChangeEvent } from '@components/General/Input';
|
|
||||||
import type { NDKEvent } from '@nostr-dev-kit/ndk';
|
import type { NDKEvent } from '@nostr-dev-kit/ndk';
|
||||||
import * as nip19 from '@nostr/tools/nip19';
|
import * as nip19 from '@nostr/tools/nip19';
|
||||||
import { LitElement, css, html } from 'lit';
|
import { LitElement, css, html } from 'lit';
|
||||||
|
@ -9,8 +8,8 @@ import { keyed } from 'lit/directives/keyed.js';
|
||||||
import { map } from 'lit/directives/map.js';
|
import { map } from 'lit/directives/map.js';
|
||||||
import { when } from 'lit/directives/when.js';
|
import { when } from 'lit/directives/when.js';
|
||||||
|
|
||||||
import '@components/General/Input';
|
|
||||||
import '@components/HeaderSugestion';
|
import '@components/HeaderSugestion';
|
||||||
|
import '@components/General/Input';
|
||||||
|
|
||||||
@customElement('arx-header')
|
@customElement('arx-header')
|
||||||
export class Header extends LitElement {
|
export class Header extends LitElement {
|
||||||
|
@ -50,10 +49,10 @@ export class Header extends LitElement {
|
||||||
.nav-buttons {
|
.nav-buttons {
|
||||||
display: flex;
|
display: flex;
|
||||||
gap: var(--space-xs, 0.5rem);
|
gap: var(--space-xs, 0.5rem);
|
||||||
padding: 0 var(--space-xs, 0.5rem);
|
padding-right: var(--space-xs, 0.5rem);
|
||||||
}
|
}
|
||||||
|
|
||||||
button {
|
.nav-buttons button {
|
||||||
text-decoration: none;
|
text-decoration: none;
|
||||||
color: var(--color-primary-content);
|
color: var(--color-primary-content);
|
||||||
background: oklch(from var(--color-primary-content) l c h / 0.1);
|
background: oklch(from var(--color-primary-content) l c h / 0.1);
|
||||||
|
@ -67,22 +66,24 @@ export class Header extends LitElement {
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
transition: all 0.2s ease;
|
transition: all 0.2s ease;
|
||||||
&:hover {
|
}
|
||||||
|
|
||||||
|
.nav-buttons button:hover {
|
||||||
background: oklch(from var(--color-primary-content) l c h / 0.2);
|
background: oklch(from var(--color-primary-content) l c h / 0.2);
|
||||||
transform: translateY(-2px);
|
transform: translateY(-2px);
|
||||||
box-shadow: calc(var(--depth) * 2px) calc(var(--depth) * 2px)
|
box-shadow: calc(var(--depth) * 2px) calc(var(--depth) * 2px)
|
||||||
calc(var(--depth) * 4px)
|
calc(var(--depth) * 4px)
|
||||||
oklch(from var(--color-base-content) l c h / 0.15);
|
oklch(from var(--color-base-content) l c h / 0.15);
|
||||||
}
|
}
|
||||||
&:active {
|
|
||||||
|
.nav-buttons button:active {
|
||||||
transform: translateY(1px);
|
transform: translateY(1px);
|
||||||
}
|
}
|
||||||
|
|
||||||
&.disabled {
|
.nav-buttons button.disabled {
|
||||||
opacity: 0.5;
|
opacity: 0.5;
|
||||||
pointer-events: none;
|
pointer-events: none;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
.search-container {
|
.search-container {
|
||||||
flex: 1;
|
flex: 1;
|
||||||
|
@ -152,7 +153,7 @@ export class Header extends LitElement {
|
||||||
placeholder=${this.url}
|
placeholder=${this.url}
|
||||||
@keyup=${this._handleSearch}
|
@keyup=${this._handleSearch}
|
||||||
@focus=${this._handleFocus}
|
@focus=${this._handleFocus}
|
||||||
@change=${this._handleInput}
|
@input=${this._handleInput}
|
||||||
></arx-input>
|
></arx-input>
|
||||||
${when(
|
${when(
|
||||||
this.showSuggestions,
|
this.showSuggestions,
|
||||||
|
@ -174,25 +175,16 @@ export class Header extends LitElement {
|
||||||
`,
|
`,
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
<div class="nav-buttons">
|
|
||||||
<button @click=${this._goToWallet}>
|
|
||||||
<iconify-icon icon="material-symbols:wallet"></iconify-icon>
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</header>
|
</header>
|
||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
|
|
||||||
private _goToWallet() {
|
|
||||||
window.location.hash = 'wallet';
|
|
||||||
}
|
|
||||||
|
|
||||||
private _handleFocus() {
|
private _handleFocus() {
|
||||||
this.showSuggestions = true;
|
this.showSuggestions = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
private _handleInput(e: ArxInputChangeEvent) {
|
private _handleInput(e: InputEvent) {
|
||||||
this.searchQuery = e.detail.value;
|
this.searchQuery = (e.target as HTMLInputElement).value;
|
||||||
if (this._debounceTimeout) {
|
if (this._debounceTimeout) {
|
||||||
clearTimeout(this._debounceTimeout);
|
clearTimeout(this._debounceTimeout);
|
||||||
}
|
}
|
||||||
|
@ -253,6 +245,8 @@ export class Header extends LitElement {
|
||||||
private _handleSearch(e: KeyboardEvent) {
|
private _handleSearch(e: KeyboardEvent) {
|
||||||
if (e.key !== 'Enter') return;
|
if (e.key !== 'Enter') return;
|
||||||
|
|
||||||
|
const target = e.target as HTMLInputElement;
|
||||||
|
this.searchQuery = target.value;
|
||||||
this.showSuggestions = false;
|
this.showSuggestions = false;
|
||||||
|
|
||||||
if (this.searchQuery.startsWith('npub1')) {
|
if (this.searchQuery.startsWith('npub1')) {
|
||||||
|
|
|
@ -1,5 +1,4 @@
|
||||||
import { ndk, setSigner } from '@/ndk';
|
import { ndk, setSigner } from '@/ndk';
|
||||||
import type { ArxInputChangeEvent } from '@components/General/Input';
|
|
||||||
import { animate } from '@lit-labs/motion';
|
import { animate } from '@lit-labs/motion';
|
||||||
import { randomBytes } from '@noble/ciphers/webcrypto';
|
import { randomBytes } from '@noble/ciphers/webcrypto';
|
||||||
import { NDKEvent, NDKKind, NDKPrivateKeySigner } from '@nostr-dev-kit/ndk';
|
import { NDKEvent, NDKKind, NDKPrivateKeySigner } from '@nostr-dev-kit/ndk';
|
||||||
|
@ -11,10 +10,10 @@ import { encodeBase64 } from '@std/encoding/base64';
|
||||||
import { LitElement, css, html } from 'lit';
|
import { LitElement, css, html } from 'lit';
|
||||||
import { customElement, state } from 'lit/decorators.js';
|
import { customElement, state } from 'lit/decorators.js';
|
||||||
|
|
||||||
import '@components/General/Button';
|
|
||||||
import '@components/General/Fieldset';
|
|
||||||
import '@components/General/Input';
|
|
||||||
import '@components/LoadingView';
|
import '@components/LoadingView';
|
||||||
|
import '@components/General/Button';
|
||||||
|
import '@components/General/Input';
|
||||||
|
import '@components/General/Fieldset';
|
||||||
|
|
||||||
@customElement('arx-initial-setup')
|
@customElement('arx-initial-setup')
|
||||||
export class InitialSetup extends LitElement {
|
export class InitialSetup extends LitElement {
|
||||||
|
@ -202,8 +201,8 @@ export class InitialSetup extends LitElement {
|
||||||
}, 300);
|
}, 300);
|
||||||
}
|
}
|
||||||
|
|
||||||
private onSeedPhraseInput(event: ArxInputChangeEvent) {
|
private onSeedPhraseInput(event: Event) {
|
||||||
this.seedPhrase = event.detail.value;
|
this.seedPhrase = (event.target as HTMLInputElement).value;
|
||||||
}
|
}
|
||||||
|
|
||||||
private generateSeedPhrase() {
|
private generateSeedPhrase() {
|
||||||
|
@ -301,7 +300,7 @@ export class InitialSetup extends LitElement {
|
||||||
</p>
|
</p>
|
||||||
<div class="input-group">
|
<div class="input-group">
|
||||||
<arx-input
|
<arx-input
|
||||||
@change=${this.onSeedPhraseInput}
|
@input=${this.onSeedPhraseInput}
|
||||||
.value=${this.seedPhrase}
|
.value=${this.seedPhrase}
|
||||||
id="seed-input"
|
id="seed-input"
|
||||||
type="text"
|
type="text"
|
||||||
|
@ -382,16 +381,16 @@ export class InitialSetup extends LitElement {
|
||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
|
|
||||||
private onUserNameInput(e: CustomEvent<{ value: string }>) {
|
private onUserNameInput(e: Event) {
|
||||||
this.userName = e.detail.value;
|
this.userName = (e.target as HTMLInputElement).value;
|
||||||
}
|
}
|
||||||
|
|
||||||
private onProfileImageInput(e: CustomEvent<{ value: string }>) {
|
private onProfileImageInput(e: Event) {
|
||||||
this.profileImage = e.detail.value;
|
this.profileImage = (e.target as HTMLInputElement).value;
|
||||||
}
|
}
|
||||||
|
|
||||||
private onLightningAddressInput(e: CustomEvent<{ value: string }>) {
|
private onLightningAddressInput(e: Event) {
|
||||||
this.lightningAddress = e.detail.value;
|
this.lightningAddress = (e.target as HTMLInputElement).value;
|
||||||
}
|
}
|
||||||
|
|
||||||
private renderPageFour() {
|
private renderPageFour() {
|
||||||
|
@ -410,7 +409,7 @@ export class InitialSetup extends LitElement {
|
||||||
id="username"
|
id="username"
|
||||||
type="text"
|
type="text"
|
||||||
.value=${this.userName}
|
.value=${this.userName}
|
||||||
@change=${this.onUserNameInput}
|
@input=${this.onUserNameInput}
|
||||||
placeholder="Enter your name"
|
placeholder="Enter your name"
|
||||||
></arx-input>
|
></arx-input>
|
||||||
</arx-fieldset>
|
</arx-fieldset>
|
||||||
|
@ -419,7 +418,7 @@ export class InitialSetup extends LitElement {
|
||||||
id="profile-image"
|
id="profile-image"
|
||||||
type="text"
|
type="text"
|
||||||
.value=${this.profileImage}
|
.value=${this.profileImage}
|
||||||
@change=${this.onProfileImageInput}
|
@input=${this.onProfileImageInput}
|
||||||
placeholder="Enter image URL"
|
placeholder="Enter image URL"
|
||||||
></arx-input>
|
></arx-input>
|
||||||
<small class="note">
|
<small class="note">
|
||||||
|
@ -465,7 +464,7 @@ export class InitialSetup extends LitElement {
|
||||||
id="lightning-address"
|
id="lightning-address"
|
||||||
type="text"
|
type="text"
|
||||||
.value=${this.lightningAddress}
|
.value=${this.lightningAddress}
|
||||||
@change=${this.onLightningAddressInput}
|
@input=${this.onLightningAddressInput}
|
||||||
placeholder="your@lightning.address"
|
placeholder="your@lightning.address"
|
||||||
/></arx-input>
|
/></arx-input>
|
||||||
<small class="note">
|
<small class="note">
|
||||||
|
|
|
@ -1,100 +0,0 @@
|
||||||
import { StateController } from '@lit-app/state';
|
|
||||||
import { LitElement, css, html } from 'lit';
|
|
||||||
import { customElement, property } from 'lit/decorators.js';
|
|
||||||
|
|
||||||
import { type WalletHistory, wallet } from '@/wallet';
|
|
||||||
|
|
||||||
import formatDateTime from '@/utils/formatDateTime';
|
|
||||||
import satsComma from '@/utils/satsComma';
|
|
||||||
import '@components/General/Card';
|
|
||||||
import '@components/General/Input';
|
|
||||||
|
|
||||||
@customElement('arx-wallet-transaction-line')
|
|
||||||
export class WalletTransactionLine extends LitElement {
|
|
||||||
@property({ type: Object }) transaction!: WalletHistory;
|
|
||||||
|
|
||||||
override connectedCallback(): void {
|
|
||||||
super.connectedCallback();
|
|
||||||
new StateController(this, wallet);
|
|
||||||
}
|
|
||||||
|
|
||||||
static override styles = css`
|
|
||||||
:host {
|
|
||||||
display: block;
|
|
||||||
width: 100%;
|
|
||||||
border-bottom: 1px solid var(--color-base-200);
|
|
||||||
container-type: inline-size;
|
|
||||||
}
|
|
||||||
|
|
||||||
.transaction {
|
|
||||||
display: grid;
|
|
||||||
grid-template-columns: 200px 1fr auto;
|
|
||||||
justify-content: space-between;
|
|
||||||
align-items: center;
|
|
||||||
padding: 10px 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.in {
|
|
||||||
color: var(--color-success);
|
|
||||||
}
|
|
||||||
|
|
||||||
.out {
|
|
||||||
color: var(--color-error);
|
|
||||||
}
|
|
||||||
|
|
||||||
.amount {
|
|
||||||
text-align: right;
|
|
||||||
font-family: var(--font-mono);
|
|
||||||
font-weight: bold;
|
|
||||||
margin-right: 10px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.date {
|
|
||||||
color: var(--color-secondary);
|
|
||||||
font-size: 0.9em;
|
|
||||||
}
|
|
||||||
|
|
||||||
.profiles {
|
|
||||||
display: flex;
|
|
||||||
gap: 10px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.sender {
|
|
||||||
order: 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
.recipient {
|
|
||||||
order: 2;
|
|
||||||
}
|
|
||||||
|
|
||||||
@container (max-width: 400px) {
|
|
||||||
.transaction {
|
|
||||||
grid-template-columns: 1fr;
|
|
||||||
}
|
|
||||||
|
|
||||||
.amount {
|
|
||||||
text-align: left;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
`;
|
|
||||||
|
|
||||||
override render() {
|
|
||||||
return html`<div class="transaction ${this.transaction.direction}">
|
|
||||||
<span class="amount">
|
|
||||||
${this.transaction.direction === 'in' ? '+' : '-'} ${satsComma(this.transaction.amount)}
|
|
||||||
sats
|
|
||||||
</span>
|
|
||||||
<span class="date">${formatDateTime(this.transaction.created_at * 1000)}</span>
|
|
||||||
<div class="profiles">
|
|
||||||
<arx-nostr-profile
|
|
||||||
pubkey=${this.transaction.senderPubkey}
|
|
||||||
class="sender"
|
|
||||||
></arx-nostr-profile>
|
|
||||||
<arx-nostr-profile
|
|
||||||
pubkey=${this.transaction.recipientPubkey}
|
|
||||||
class="recipient"
|
|
||||||
></arx-nostr-profile>
|
|
||||||
</div>
|
|
||||||
</div>`;
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,82 +0,0 @@
|
||||||
import satsComma from '@/utils/satsComma';
|
|
||||||
import { wallet } from '@/wallet';
|
|
||||||
import '@components/General/Fieldset';
|
|
||||||
import '@components/LoadingView';
|
|
||||||
import '@components/WalletTransactionLine';
|
|
||||||
import { StateController } from '@lit-app/state';
|
|
||||||
import { LitElement, css, html } from 'lit';
|
|
||||||
import { customElement, state } from 'lit/decorators.js';
|
|
||||||
import { when } from 'lit/directives/when.js';
|
|
||||||
|
|
||||||
@customElement('arx-wallet-widget')
|
|
||||||
export class WalletWidget extends LitElement {
|
|
||||||
@state()
|
|
||||||
public loading = false;
|
|
||||||
|
|
||||||
override connectedCallback(): void {
|
|
||||||
super.connectedCallback();
|
|
||||||
new StateController(this, wallet);
|
|
||||||
}
|
|
||||||
|
|
||||||
override async firstUpdated() {
|
|
||||||
this.loading = true;
|
|
||||||
await wallet.loadWallet();
|
|
||||||
this.loading = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
static override styles = [
|
|
||||||
css`
|
|
||||||
:host {
|
|
||||||
display: block;
|
|
||||||
}
|
|
||||||
|
|
||||||
.widget-header {
|
|
||||||
display: flex;
|
|
||||||
justify-content: space-between;
|
|
||||||
align-items: center;
|
|
||||||
margin-bottom: 12px;
|
|
||||||
border-bottom: var(--border) solid var(--color-base-300);
|
|
||||||
padding-bottom: 8px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.widget-title {
|
|
||||||
font-size: 1.2rem;
|
|
||||||
font-weight: 600;
|
|
||||||
margin: 0;
|
|
||||||
color: var(--color-base-content);
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
gap: 8px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.bitcoin-icon {
|
|
||||||
color: var(--color-warning);
|
|
||||||
font-size: 1.4rem;
|
|
||||||
}
|
|
||||||
`,
|
|
||||||
];
|
|
||||||
|
|
||||||
override render() {
|
|
||||||
return html`
|
|
||||||
<div class="widget-header">
|
|
||||||
<h3 class="widget-title">
|
|
||||||
<span class="bitcoin-icon">₿</span> Bitcoin Wallet
|
|
||||||
</h3>
|
|
||||||
</div>
|
|
||||||
<arx-fieldset .legend=${when(
|
|
||||||
this.loading,
|
|
||||||
() => 'Loading...',
|
|
||||||
() => `${satsComma(wallet.balance)} sats`,
|
|
||||||
)}>
|
|
||||||
${when(
|
|
||||||
wallet.sortedHistory.length > 0,
|
|
||||||
() => html`
|
|
||||||
Latest Transaction:
|
|
||||||
<arx-wallet-transaction-line .transaction=${wallet.sortedHistory[0]}></arx-wallet-transaction-line>
|
|
||||||
`,
|
|
||||||
() => 'No transactions yet',
|
|
||||||
)}
|
|
||||||
</arx-fieldset>
|
|
||||||
`;
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,4 +1,3 @@
|
||||||
import type { ArxInputChangeEvent } from '@/components/General/Input';
|
|
||||||
import { getSigner, ndk } from '@/ndk';
|
import { getSigner, ndk } from '@/ndk';
|
||||||
import { NDKEvent } from '@nostr-dev-kit/ndk';
|
import { NDKEvent } from '@nostr-dev-kit/ndk';
|
||||||
import { LitElement, css, html } from 'lit';
|
import { LitElement, css, html } from 'lit';
|
||||||
|
@ -107,8 +106,8 @@ export class ArborPostCreator extends LitElement {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private handleContentInput(e: ArxInputChangeEvent) {
|
private handleContentInput(e: InputEvent) {
|
||||||
this.postContent = e.detail.value;
|
this.postContent = (e.target as HTMLTextAreaElement).value;
|
||||||
if (this.error && this.postContent.length >= 10) {
|
if (this.error && this.postContent.length >= 10) {
|
||||||
this.error = null;
|
this.error = null;
|
||||||
}
|
}
|
||||||
|
@ -122,7 +121,7 @@ export class ArborPostCreator extends LitElement {
|
||||||
<arx-textarea
|
<arx-textarea
|
||||||
placeholder="Post. You can use Markdown here."
|
placeholder="Post. You can use Markdown here."
|
||||||
.value=${this.postContent}
|
.value=${this.postContent}
|
||||||
@change=${this.handleContentInput}
|
@input=${this.handleContentInput}
|
||||||
?disabled=${this.isCreating}
|
?disabled=${this.isCreating}
|
||||||
></arx-textarea>
|
></arx-textarea>
|
||||||
|
|
||||||
|
|
|
@ -1,12 +1,11 @@
|
||||||
import { getSigner, ndk } from '@/ndk';
|
import { getSigner, ndk } from '@/ndk';
|
||||||
import type { ArxInputChangeEvent } from '@components/General/Input';
|
|
||||||
import { NDKEvent } from '@nostr-dev-kit/ndk';
|
import { NDKEvent } from '@nostr-dev-kit/ndk';
|
||||||
import { LitElement, css, html } from 'lit';
|
import { LitElement, css, html } from 'lit';
|
||||||
import { customElement, property, state } from 'lit/decorators.js';
|
import { customElement, property, state } from 'lit/decorators.js';
|
||||||
|
|
||||||
import '@components/General/Button';
|
|
||||||
import '@components/General/Input';
|
import '@components/General/Input';
|
||||||
import '@components/General/Textarea';
|
import '@components/General/Textarea';
|
||||||
|
import '@components/General/Button';
|
||||||
|
|
||||||
@customElement('arx-arbor-topic-creator')
|
@customElement('arx-arbor-topic-creator')
|
||||||
export class ArborTopicCreator extends LitElement {
|
export class ArborTopicCreator extends LitElement {
|
||||||
|
@ -87,12 +86,12 @@ export class ArborTopicCreator extends LitElement {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private handleTopicInput(e: ArxInputChangeEvent) {
|
private handleTopicInput(e: InputEvent) {
|
||||||
this.newTopic = e.detail.value;
|
this.newTopic = (e.target as HTMLInputElement).value;
|
||||||
}
|
}
|
||||||
|
|
||||||
private handleContentInput(e: ArxInputChangeEvent) {
|
private handleContentInput(e: InputEvent) {
|
||||||
this.topicContent = e.detail.value;
|
this.topicContent = (e.target as HTMLTextAreaElement).value;
|
||||||
}
|
}
|
||||||
|
|
||||||
override render() {
|
override render() {
|
||||||
|
@ -103,14 +102,14 @@ export class ArborTopicCreator extends LitElement {
|
||||||
type="text"
|
type="text"
|
||||||
placeholder="New Topic"
|
placeholder="New Topic"
|
||||||
.value=${this.newTopic}
|
.value=${this.newTopic}
|
||||||
@change=${this.handleTopicInput}
|
@input=${this.handleTopicInput}
|
||||||
?disabled=${this.isCreating}
|
?disabled=${this.isCreating}
|
||||||
></arx-input>
|
></arx-input>
|
||||||
|
|
||||||
<arx-textarea
|
<arx-textarea
|
||||||
placeholder="Topic. You can use Markdown here."
|
placeholder="Topic. You can use Markdown here."
|
||||||
.value=${this.topicContent}
|
.value=${this.topicContent}
|
||||||
@change=${this.handleContentInput}
|
@input=${this.handleContentInput}
|
||||||
?disabled=${this.isCreating}
|
?disabled=${this.isCreating}
|
||||||
></arx-textarea>
|
></arx-textarea>
|
||||||
|
|
||||||
|
|
|
@ -5,11 +5,10 @@ import { customElement, state } from 'lit/decorators.js';
|
||||||
import { map } from 'lit/directives/map.js';
|
import { map } from 'lit/directives/map.js';
|
||||||
import { html, literal } from 'lit/static-html.js';
|
import { html, literal } from 'lit/static-html.js';
|
||||||
|
|
||||||
|
import '@widgets/BitcoinBlockWidget';
|
||||||
import '@components/AppGrid';
|
import '@components/AppGrid';
|
||||||
import '@components/General/Card';
|
|
||||||
import '@components/NostrAvatar';
|
import '@components/NostrAvatar';
|
||||||
import '@components/Widgets/BitcoinBlockWidget';
|
import '@components/General/Card';
|
||||||
import '@components/Widgets/WalletWidget';
|
|
||||||
|
|
||||||
@customElement('arx-eve-home')
|
@customElement('arx-eve-home')
|
||||||
export class Home extends LitElement {
|
export class Home extends LitElement {
|
||||||
|
@ -84,10 +83,6 @@ export class Home extends LitElement {
|
||||||
title: 'Bitcoin Block',
|
title: 'Bitcoin Block',
|
||||||
content: literal`arx-bitcoin-block-widget`,
|
content: literal`arx-bitcoin-block-widget`,
|
||||||
},
|
},
|
||||||
{
|
|
||||||
title: 'Bitcoin Wallet',
|
|
||||||
content: literal`arx-wallet-widget`,
|
|
||||||
},
|
|
||||||
];
|
];
|
||||||
|
|
||||||
async loadProperties() {
|
async loadProperties() {
|
||||||
|
@ -100,68 +95,57 @@ export class Home extends LitElement {
|
||||||
|
|
||||||
static override styles = [
|
static override styles = [
|
||||||
css`
|
css`
|
||||||
::-webkit-scrollbar {
|
|
||||||
width: 12px;
|
|
||||||
height: 12px;
|
|
||||||
}
|
|
||||||
|
|
||||||
::-webkit-scrollbar-track {
|
|
||||||
background: var(--color-base-200);
|
|
||||||
border-radius: var(--radius-field);
|
|
||||||
}
|
|
||||||
|
|
||||||
::-webkit-scrollbar-thumb {
|
|
||||||
background: var(--color-base-300);
|
|
||||||
border-radius: var(--radius-field);
|
|
||||||
border: 2px solid var(--color-base-200);
|
|
||||||
transition: var(--transition);
|
|
||||||
}
|
|
||||||
|
|
||||||
::-webkit-scrollbar-thumb:hover {
|
|
||||||
background: var(--color-neutral);
|
|
||||||
}
|
|
||||||
|
|
||||||
.content-wrapper {
|
.content-wrapper {
|
||||||
margin: auto;
|
|
||||||
height: 90vh;
|
|
||||||
overflow: hidden;
|
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
gap: 20px;
|
gap: 20px;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
max-width: 1200px;
|
max-width: 1200px;
|
||||||
|
margin: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.home {
|
||||||
|
min-height: calc(100vh - var(--font-2xl));
|
||||||
|
width: 100%;
|
||||||
|
position: absolute;
|
||||||
|
right: 0;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
.home-container {
|
.home-container {
|
||||||
flex: 1;
|
flex: 1;
|
||||||
padding: 0;
|
padding: 0;
|
||||||
height: auto;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.widgets-container {
|
.widgets-container {
|
||||||
height: 75vh;
|
|
||||||
overflow: auto;
|
|
||||||
width: 350px;
|
width: 350px;
|
||||||
margin: 0 auto;
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 20px;
|
||||||
|
|
||||||
|
& > * {
|
||||||
|
flex: 1;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@media (max-width: 1024px) {
|
@media (max-width: 1024px) {
|
||||||
.content-wrapper {
|
.content-wrapper {
|
||||||
gap: 0;
|
|
||||||
height: auto;
|
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
}
|
}
|
||||||
|
|
||||||
.home-container {
|
.widgets-container {
|
||||||
width: 100%;
|
width: calc(100vw - 40px);
|
||||||
|
flex-direction: row;
|
||||||
|
overflow-x: auto;
|
||||||
|
padding-bottom: 10px;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@media (max-width: 768px) {
|
||||||
.widgets-container {
|
.widgets-container {
|
||||||
overflow: unset;
|
flex-direction: column;
|
||||||
height: auto;
|
|
||||||
width: calc(100vw - 40px);
|
|
||||||
padding-bottom: 10px;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -189,6 +173,7 @@ export class Home extends LitElement {
|
||||||
|
|
||||||
override render() {
|
override render() {
|
||||||
return html`
|
return html`
|
||||||
|
<div class="home">
|
||||||
<div class="content-wrapper">
|
<div class="content-wrapper">
|
||||||
<arx-card class="home-container">
|
<arx-card class="home-container">
|
||||||
<arx-card class="welcome-section">
|
<arx-card class="welcome-section">
|
||||||
|
@ -206,6 +191,7 @@ export class Home extends LitElement {
|
||||||
${map(this.widgets, (widget) => html`<arx-card><${widget.content}></${widget.content}></arx-card>`)}
|
${map(this.widgets, (widget) => html`<arx-card><${widget.content}></${widget.content}></arx-card>`)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,17 +1,16 @@
|
||||||
import defaultAvatar from '@/default-avatar.png';
|
import defaultAvatar from '@/default-avatar.png';
|
||||||
import { getSigner, getUserProfile, ndk } from '@/ndk';
|
import { getSigner, getUserProfile, ndk } from '@/ndk';
|
||||||
import type { ArxInputChangeEvent } from '@components/General/Input';
|
|
||||||
import { NDKEvent, type NDKUserProfile } from '@nostr-dev-kit/ndk';
|
import { NDKEvent, type NDKUserProfile } from '@nostr-dev-kit/ndk';
|
||||||
import { LitElement, css, html } from 'lit';
|
import { LitElement, css, html } from 'lit';
|
||||||
import { customElement, state } from 'lit/decorators.js';
|
import { customElement, state } from 'lit/decorators.js';
|
||||||
import { when } from 'lit/directives/when.js';
|
import { when } from 'lit/directives/when.js';
|
||||||
|
|
||||||
import '@components/Breadcrumbs';
|
|
||||||
import '@components/DateTimeSettings';
|
import '@components/DateTimeSettings';
|
||||||
import '@components/General/Button';
|
|
||||||
import '@components/General/Card';
|
|
||||||
import '@components/General/Fieldset';
|
|
||||||
import '@components/General/Input';
|
import '@components/General/Input';
|
||||||
|
import '@components/General/Button';
|
||||||
|
import '@components/General/Fieldset';
|
||||||
|
import '@components/General/Card';
|
||||||
|
import '@components/Breadcrumbs';
|
||||||
|
|
||||||
@customElement('arx-settings')
|
@customElement('arx-settings')
|
||||||
export class EveSettings extends LitElement {
|
export class EveSettings extends LitElement {
|
||||||
|
@ -74,10 +73,11 @@ export class EveSettings extends LitElement {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private handleInputChange(field: string, e: ArxInputChangeEvent) {
|
private handleInputChange(e: Event) {
|
||||||
|
const target = e.target as HTMLInputElement;
|
||||||
this.profile = {
|
this.profile = {
|
||||||
...this.profile,
|
...this.profile,
|
||||||
[field]: e.detail.value,
|
[target.name]: target.value,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -144,7 +144,7 @@ export class EveSettings extends LitElement {
|
||||||
type="text"
|
type="text"
|
||||||
name="name"
|
name="name"
|
||||||
.value=${this.profile.name}
|
.value=${this.profile.name}
|
||||||
@change=${(e: ArxInputChangeEvent) => this.handleInputChange('name', e)}
|
@input=${this.handleInputChange}
|
||||||
placeholder="Your display name"
|
placeholder="Your display name"
|
||||||
></arx-input>
|
></arx-input>
|
||||||
|
|
||||||
|
@ -153,7 +153,7 @@ export class EveSettings extends LitElement {
|
||||||
type="text"
|
type="text"
|
||||||
name="image"
|
name="image"
|
||||||
.value=${this.profile.picture}
|
.value=${this.profile.picture}
|
||||||
@change=${(e: ArxInputChangeEvent) => this.handleInputChange('picture', e)}
|
@input=${this.handleInputChange}
|
||||||
placeholder="https://example.com/your-image.jpg"
|
placeholder="https://example.com/your-image.jpg"
|
||||||
></arx-input>
|
></arx-input>
|
||||||
|
|
||||||
|
@ -162,7 +162,7 @@ export class EveSettings extends LitElement {
|
||||||
type="text"
|
type="text"
|
||||||
name="banner"
|
name="banner"
|
||||||
.value=${this.profile.banner}
|
.value=${this.profile.banner}
|
||||||
@change=${(e: ArxInputChangeEvent) => this.handleInputChange('banner', e)}
|
@input=${this.handleInputChange}
|
||||||
placeholder="https://example.com/your-image.jpg"
|
placeholder="https://example.com/your-image.jpg"
|
||||||
></arx-input>
|
></arx-input>
|
||||||
</arx-fieldset>
|
</arx-fieldset>
|
||||||
|
|
|
@ -1,78 +0,0 @@
|
||||||
import { wallet } from '@/wallet';
|
|
||||||
import type { ArxInputChangeEvent } from '@components/General/Input';
|
|
||||||
import { StateController } from '@lit-app/state';
|
|
||||||
import { LitElement, css, html } from 'lit';
|
|
||||||
import { customElement } from 'lit/decorators.js';
|
|
||||||
|
|
||||||
import '@components/General/Card';
|
|
||||||
import '@components/General/Input';
|
|
||||||
import '@components/WalletTransactionLine';
|
|
||||||
|
|
||||||
@customElement('arx-wallet-route')
|
|
||||||
export class WalletRoute extends LitElement {
|
|
||||||
static override styles = css`
|
|
||||||
h1 {
|
|
||||||
text-align: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
.transaction-list {
|
|
||||||
background-color: var(--color-base-100);
|
|
||||||
border-radius: var(--radius-card);
|
|
||||||
padding: 20px;
|
|
||||||
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
|
|
||||||
}
|
|
||||||
|
|
||||||
.transaction-list h3 {
|
|
||||||
color: var(--color-base-content);
|
|
||||||
margin-bottom: 20px;
|
|
||||||
}
|
|
||||||
|
|
||||||
arx-wallet-transaction-line:last-child {
|
|
||||||
border-bottom: none;
|
|
||||||
}
|
|
||||||
`;
|
|
||||||
|
|
||||||
private inputToken = '';
|
|
||||||
|
|
||||||
override async firstUpdated() {
|
|
||||||
await wallet.loadWallet();
|
|
||||||
}
|
|
||||||
|
|
||||||
async doReceiveToken() {
|
|
||||||
console.log('clicked', Date.now());
|
|
||||||
await wallet.receiveToken(this.inputToken);
|
|
||||||
this.inputToken = '';
|
|
||||||
}
|
|
||||||
|
|
||||||
override connectedCallback(): void {
|
|
||||||
super.connectedCallback();
|
|
||||||
new StateController(this, wallet);
|
|
||||||
}
|
|
||||||
|
|
||||||
override render() {
|
|
||||||
return html`
|
|
||||||
<arx-card>
|
|
||||||
<h1>Wallet</h1>
|
|
||||||
<hr />
|
|
||||||
<h2>You have ${wallet.balance} sats</h2>
|
|
||||||
<arx-input
|
|
||||||
label="Token"
|
|
||||||
.value=${this.inputToken}
|
|
||||||
@change=${(e: ArxInputChangeEvent) => {
|
|
||||||
if (!e.detail.value) return;
|
|
||||||
this.inputToken = e.detail.value;
|
|
||||||
}}
|
|
||||||
type="text"
|
|
||||||
id="token"
|
|
||||||
></arx-input>
|
|
||||||
<arx-button label="Receive" @click=${this.doReceiveToken}></arx-button>
|
|
||||||
<div class="transaction-list">
|
|
||||||
<h3>Transaction History</h3>
|
|
||||||
${wallet.sortedHistory.map(
|
|
||||||
(h) => html`<arx-wallet-transaction-line .transaction=${h}></arx-wallet-transaction-line>`,
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
</arx-card>
|
|
||||||
`;
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,13 +1,12 @@
|
||||||
import '@components/InitialSetup';
|
|
||||||
import '@routes/404Page';
|
import '@routes/404Page';
|
||||||
import '@routes/Arbor/Home';
|
|
||||||
import '@routes/Arbor/NewPost';
|
|
||||||
import '@routes/Arbor/NewTopic';
|
|
||||||
import '@routes/Arbor/TopicView';
|
|
||||||
import '@routes/Home';
|
import '@routes/Home';
|
||||||
import '@routes/Profile';
|
import '@routes/Profile';
|
||||||
import '@routes/Settings';
|
import '@routes/Settings';
|
||||||
import '@routes/Wallet';
|
import '@routes/Arbor/Home';
|
||||||
|
import '@routes/Arbor/NewTopic';
|
||||||
|
import '@routes/Arbor/TopicView';
|
||||||
|
import '@routes/Arbor/NewPost';
|
||||||
|
import '@components/InitialSetup';
|
||||||
|
|
||||||
import { spread } from '@open-wc/lit-helpers';
|
import { spread } from '@open-wc/lit-helpers';
|
||||||
import { LitElement, css } from 'lit';
|
import { LitElement, css } from 'lit';
|
||||||
|
@ -66,11 +65,6 @@ export default class EveRouter extends LitElement {
|
||||||
params: {},
|
params: {},
|
||||||
component: literal`arx-settings`,
|
component: literal`arx-settings`,
|
||||||
},
|
},
|
||||||
{
|
|
||||||
pattern: 'wallet',
|
|
||||||
params: {},
|
|
||||||
component: literal`arx-wallet-route`,
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
pattern: '404',
|
pattern: '404',
|
||||||
params: {},
|
params: {},
|
||||||
|
@ -98,27 +92,6 @@ export default class EveRouter extends LitElement {
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
}
|
}
|
||||||
|
|
||||||
::-webkit-scrollbar {
|
|
||||||
width: 12px;
|
|
||||||
height: 12px;
|
|
||||||
}
|
|
||||||
|
|
||||||
::-webkit-scrollbar-track {
|
|
||||||
background: var(--color-base-200);
|
|
||||||
border-radius: var(--radius-field);
|
|
||||||
}
|
|
||||||
|
|
||||||
::-webkit-scrollbar-thumb {
|
|
||||||
background: var(--color-base-300);
|
|
||||||
border-radius: var(--radius-field);
|
|
||||||
border: 2px solid var(--color-base-200);
|
|
||||||
transition: var(--transition);
|
|
||||||
}
|
|
||||||
|
|
||||||
::-webkit-scrollbar-thumb:hover {
|
|
||||||
background: var(--color-neutral);
|
|
||||||
}
|
|
||||||
|
|
||||||
.window {
|
.window {
|
||||||
overflow: auto;
|
overflow: auto;
|
||||||
}
|
}
|
||||||
|
@ -130,10 +103,6 @@ export default class EveRouter extends LitElement {
|
||||||
margin: 0 auto;
|
margin: 0 auto;
|
||||||
padding: 1rem;
|
padding: 1rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
.hide-overflow {
|
|
||||||
overflow: hidden;
|
|
||||||
}
|
|
||||||
`;
|
`;
|
||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
|
@ -299,7 +268,7 @@ export default class EveRouter extends LitElement {
|
||||||
@go-forward=${this.goForward}
|
@go-forward=${this.goForward}
|
||||||
title="Eve"
|
title="Eve"
|
||||||
></arx-header>
|
></arx-header>
|
||||||
<div class="window ${this.currentRoute.pattern === 'home' ? 'hide-overflow' : ''}">
|
<div class="window">
|
||||||
<div class="window-content">
|
<div class="window-content">
|
||||||
${keyed(
|
${keyed(
|
||||||
this.currentRoute.params,
|
this.currentRoute.params,
|
||||||
|
|
|
@ -43,8 +43,6 @@
|
||||||
--border: 2px;
|
--border: 2px;
|
||||||
--depth: 1;
|
--depth: 1;
|
||||||
--noise: 1;
|
--noise: 1;
|
||||||
|
|
||||||
--font-mono: 'SF Mono', 'Monaco', 'Inconsolata', 'Fira Mono', 'Droid Sans Mono', 'Source Code Pro', monospace;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
body.dark {
|
body.dark {
|
||||||
|
|
306
src/wallet.ts
306
src/wallet.ts
|
@ -1,306 +0,0 @@
|
||||||
import { CashuMint, CashuWallet, type Proof, type SendOptions } from '@cashu/cashu-ts';
|
|
||||||
import { bytesToHex } from '@noble/ciphers/utils';
|
|
||||||
import { secp256k1 } from '@noble/curves/secp256k1';
|
|
||||||
import { NDKEvent, type NDKEventId, NDKKind, type NDKTag, type NDKUser } from '@nostr-dev-kit/ndk';
|
|
||||||
import { getSigner, ndk } from './ndk';
|
|
||||||
|
|
||||||
import { State, property } from '@lit-app/state';
|
|
||||||
|
|
||||||
export type WalletHistory = {
|
|
||||||
id: string;
|
|
||||||
amount: number;
|
|
||||||
created_at: number;
|
|
||||||
direction: 'in' | 'out';
|
|
||||||
tokenEventId: NDKEventId;
|
|
||||||
nutZapEventId?: NDKEventId;
|
|
||||||
senderPubkey?: string;
|
|
||||||
recipientPubkey: string;
|
|
||||||
};
|
|
||||||
|
|
||||||
class Wallet extends State {
|
|
||||||
static mintUrl = 'https://testnut.cashu.space'; // TODO: this should be user controlled
|
|
||||||
|
|
||||||
private walletEvent: NDKEvent | null = null;
|
|
||||||
private cashuWallet: CashuWallet = new CashuWallet(new CashuMint(Wallet.mintUrl));
|
|
||||||
|
|
||||||
private proofs: Proof[] = [];
|
|
||||||
private eventIdToProofIdMap: Map<NDKEventId, [string, string][]> = new Map();
|
|
||||||
|
|
||||||
private isHandlingZapEvent = false;
|
|
||||||
private zapEventsQueue: NDKEvent[] = [];
|
|
||||||
private latestHistoryTimestamp = 0;
|
|
||||||
|
|
||||||
@property() public balance = 0;
|
|
||||||
@property() public history: WalletHistory[] = [];
|
|
||||||
@property() public sortedHistory: WalletHistory[] = [];
|
|
||||||
|
|
||||||
async loadWallet() {
|
|
||||||
if (this.walletEvent) return;
|
|
||||||
await getSigner();
|
|
||||||
const user = await ndk.signer!.user()!;
|
|
||||||
const privateWalletEvent = await ndk.fetchEvent({
|
|
||||||
kinds: [17375 as NDKKind],
|
|
||||||
authors: [user.pubkey],
|
|
||||||
});
|
|
||||||
if (!privateWalletEvent) {
|
|
||||||
const walletPrivateKey = secp256k1.utils.randomPrivateKey();
|
|
||||||
const walletPublicKey = secp256k1.getPublicKey(walletPrivateKey);
|
|
||||||
const newPrivateWalletEvent = new NDKEvent(ndk);
|
|
||||||
newPrivateWalletEvent.kind = NDKKind.CashuWallet;
|
|
||||||
newPrivateWalletEvent.content = await ndk.signer!.encrypt(
|
|
||||||
user,
|
|
||||||
JSON.stringify([
|
|
||||||
['privkey', bytesToHex(walletPrivateKey)],
|
|
||||||
['mint', Wallet.mintUrl],
|
|
||||||
]),
|
|
||||||
'nip44',
|
|
||||||
);
|
|
||||||
const publicWalletEvent = new NDKEvent(ndk);
|
|
||||||
publicWalletEvent.kind = NDKKind.CashuMintList;
|
|
||||||
publicWalletEvent.content = '';
|
|
||||||
publicWalletEvent.tags = [
|
|
||||||
['mint', Wallet.mintUrl, 'sat'],
|
|
||||||
['pubkey', bytesToHex(walletPublicKey)],
|
|
||||||
];
|
|
||||||
await newPrivateWalletEvent.sign();
|
|
||||||
await newPrivateWalletEvent.publish();
|
|
||||||
await publicWalletEvent.sign();
|
|
||||||
await publicWalletEvent.publish();
|
|
||||||
this.walletEvent = newPrivateWalletEvent;
|
|
||||||
} else this.walletEvent = privateWalletEvent;
|
|
||||||
|
|
||||||
const walletHistory = await ndk.fetchEvents({
|
|
||||||
kinds: [NDKKind.CashuWalletTx],
|
|
||||||
authors: [user.pubkey],
|
|
||||||
since: this.walletEvent?.created_at,
|
|
||||||
});
|
|
||||||
|
|
||||||
for (const event of walletHistory) {
|
|
||||||
await this.processHistoryEvent(event);
|
|
||||||
}
|
|
||||||
|
|
||||||
const proofsEvents = await ndk.fetchEvents({
|
|
||||||
kinds: [NDKKind.CashuToken],
|
|
||||||
authors: [user.pubkey],
|
|
||||||
});
|
|
||||||
|
|
||||||
for (const proofsEvent of proofsEvents) {
|
|
||||||
const {
|
|
||||||
mint,
|
|
||||||
proofs = [],
|
|
||||||
del = [],
|
|
||||||
} = JSON.parse(await ndk.signer!.decrypt(user, proofsEvent.content, 'nip44')) as {
|
|
||||||
mint: string;
|
|
||||||
proofs: Proof[];
|
|
||||||
del?: NDKEventId[];
|
|
||||||
};
|
|
||||||
|
|
||||||
if (mint !== this.cashuWallet.mint.mintUrl) continue;
|
|
||||||
|
|
||||||
for (const eventId of del) {
|
|
||||||
const toDeletes = this.eventIdToProofIdMap.get(eventId);
|
|
||||||
if (toDeletes)
|
|
||||||
for (const proof of toDeletes)
|
|
||||||
this.proofs = this.proofs.filter((p) => p.secret !== proof[0] || p.C !== proof[1]);
|
|
||||||
this.eventIdToProofIdMap.delete(eventId);
|
|
||||||
}
|
|
||||||
|
|
||||||
for (const proof of proofs) this.proofs.push(proof);
|
|
||||||
|
|
||||||
this.eventIdToProofIdMap.set(
|
|
||||||
proofsEvent.id,
|
|
||||||
proofs.map((p) => [p.secret, p.C]),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
ndk
|
|
||||||
.subscribe({
|
|
||||||
kinds: [NDKKind.Nutzap],
|
|
||||||
'#p': [user.pubkey],
|
|
||||||
since: this.latestHistoryTimestamp,
|
|
||||||
})
|
|
||||||
.on('event', async (event) => {
|
|
||||||
this.zapEventsQueue.push(event);
|
|
||||||
this.handleZapEvent();
|
|
||||||
});
|
|
||||||
|
|
||||||
ndk
|
|
||||||
.subscribe({
|
|
||||||
kinds: [NDKKind.CashuWalletTx],
|
|
||||||
authors: [user.pubkey],
|
|
||||||
since: this.latestHistoryTimestamp,
|
|
||||||
})
|
|
||||||
.on('event', async (event) => {
|
|
||||||
await this.processHistoryEvent(event);
|
|
||||||
});
|
|
||||||
|
|
||||||
this.recalculateBalance();
|
|
||||||
}
|
|
||||||
|
|
||||||
private async processHistoryEvent(event: NDKEvent) {
|
|
||||||
const user = await ndk.signer!.user()!;
|
|
||||||
const parsedContent = JSON.parse(await ndk.signer!.decrypt(user, event.content, 'nip44'));
|
|
||||||
const direction = parsedContent.find((p: [string, string]) => p[0] === 'direction')?.[1];
|
|
||||||
const amount = parsedContent.find((p: [string, string]) => p[0] === 'amount')?.[1];
|
|
||||||
const tokenEventId = parsedContent.find((p: [string, string]) => p[0] === 'e')?.[1];
|
|
||||||
const nutZapEventId = event.tags.find((t: NDKTag) => t[0] === 'e')?.[1];
|
|
||||||
const senderPubkey = event.tags.find((t: NDKTag) => t[0] === 'p')?.[1];
|
|
||||||
if (!nutZapEventId || !senderPubkey) return;
|
|
||||||
this.history.push({
|
|
||||||
id: event.id,
|
|
||||||
amount: Number.parseInt(amount),
|
|
||||||
created_at: event.created_at ?? 0,
|
|
||||||
direction: direction as 'in' | 'out',
|
|
||||||
tokenEventId,
|
|
||||||
nutZapEventId: nutZapEventId as NDKEventId,
|
|
||||||
senderPubkey,
|
|
||||||
recipientPubkey: event.pubkey,
|
|
||||||
});
|
|
||||||
this.latestHistoryTimestamp = Math.max(this.latestHistoryTimestamp, event.created_at ?? 0);
|
|
||||||
this.sortedHistory = [...this.history.sort((a, b) => b.created_at - a.created_at)];
|
|
||||||
}
|
|
||||||
|
|
||||||
private async handleZapEvent() {
|
|
||||||
if (this.isHandlingZapEvent) return;
|
|
||||||
const event = this.zapEventsQueue.shift();
|
|
||||||
if (!event) return;
|
|
||||||
this.isHandlingZapEvent = true;
|
|
||||||
const proofs = JSON.parse(event.tags.find((t) => t[0] === 'proof')?.[1] ?? '[]') as Proof[];
|
|
||||||
this.proofs.push(...proofs);
|
|
||||||
|
|
||||||
const newProofsEvent = await this.createProofsEvent();
|
|
||||||
await this.createHistoryEvent(
|
|
||||||
newProofsEvent,
|
|
||||||
this.proofs.reduce((acc, curr) => acc + curr.amount, 0),
|
|
||||||
'in',
|
|
||||||
);
|
|
||||||
this.recalculateBalance();
|
|
||||||
this.isHandlingZapEvent = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
private async createProofsEvent(): Promise<NDKEvent> {
|
|
||||||
const newProofsEvent = new NDKEvent(ndk);
|
|
||||||
newProofsEvent.kind = NDKKind.CashuToken;
|
|
||||||
newProofsEvent.content = await ndk.signer?.encrypt(
|
|
||||||
await ndk.signer!.user()!,
|
|
||||||
JSON.stringify({
|
|
||||||
mint: this.cashuWallet.mint.mintUrl,
|
|
||||||
proofs: this.proofs,
|
|
||||||
del: Array.from(this.eventIdToProofIdMap.keys()),
|
|
||||||
}),
|
|
||||||
'nip44',
|
|
||||||
)!;
|
|
||||||
await newProofsEvent.sign();
|
|
||||||
await newProofsEvent.publish();
|
|
||||||
return newProofsEvent;
|
|
||||||
}
|
|
||||||
|
|
||||||
recalculateBalance() {
|
|
||||||
this.proofs = this.proofs.filter(
|
|
||||||
(p, index, self) => index === self.findIndex((t) => t.secret === p.secret && t.C === p.C),
|
|
||||||
);
|
|
||||||
this.balance = this.proofs.reduce((acc, curr) => acc + curr.amount, 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
private async createHistoryEvent(newProofsEvent: NDKEvent, amount: number, direction: 'in' | 'out') {
|
|
||||||
const historyEvent = new NDKEvent(ndk);
|
|
||||||
historyEvent.kind = NDKKind.CashuWalletTx;
|
|
||||||
historyEvent.content = await ndk.signer?.encrypt(
|
|
||||||
await ndk.signer!.user()!,
|
|
||||||
JSON.stringify([
|
|
||||||
['direction', direction],
|
|
||||||
['amount', amount.toString()],
|
|
||||||
['e', newProofsEvent.id],
|
|
||||||
]),
|
|
||||||
'nip44',
|
|
||||||
)!;
|
|
||||||
historyEvent.tags = [
|
|
||||||
['e', newProofsEvent.id],
|
|
||||||
['p', newProofsEvent.pubkey],
|
|
||||||
];
|
|
||||||
await historyEvent.sign();
|
|
||||||
await historyEvent.publish();
|
|
||||||
}
|
|
||||||
|
|
||||||
/** @throws Error if token is invalid */
|
|
||||||
async receiveToken(token: string) {
|
|
||||||
const proofs = await this.cashuWallet.receive(token);
|
|
||||||
if (proofs.length === 0) throw new Error('Invalid token');
|
|
||||||
for (const proof of proofs) this.proofs.push(proof);
|
|
||||||
const newProofsEvent = await this.createProofsEvent();
|
|
||||||
const amount = proofs.reduce((acc, curr) => acc + curr.amount, 0);
|
|
||||||
await this.createHistoryEvent(newProofsEvent, amount, 'in');
|
|
||||||
this.recalculateBalance();
|
|
||||||
}
|
|
||||||
|
|
||||||
private async createToken(amount: number, recipient?: NDKUser) {
|
|
||||||
if (amount < 10) throw new Error('Amount must be greater than 10');
|
|
||||||
const sendOptions: SendOptions = {
|
|
||||||
includeFees: true,
|
|
||||||
};
|
|
||||||
if (recipient) {
|
|
||||||
const mintListEvent = await ndk.fetchEvent({
|
|
||||||
kinds: [NDKKind.CashuMintList],
|
|
||||||
authors: [recipient.pubkey],
|
|
||||||
});
|
|
||||||
if (!mintListEvent) throw new Error('Recipient has no mint list');
|
|
||||||
const pubkey = mintListEvent.tags.find((t) => t[0] === 'pubkey')?.[1];
|
|
||||||
if (!pubkey) throw new Error('Recipient has no mint list');
|
|
||||||
sendOptions.pubkey = pubkey;
|
|
||||||
}
|
|
||||||
await this.cashuWallet.getKeySets();
|
|
||||||
const sendResult = await this.cashuWallet.send(amount, this.proofs, sendOptions);
|
|
||||||
this.proofs = sendResult.keep;
|
|
||||||
const proofsEvent = new NDKEvent(ndk);
|
|
||||||
proofsEvent.kind = NDKKind.CashuToken;
|
|
||||||
proofsEvent.content = await ndk.signer?.encrypt(
|
|
||||||
await ndk.signer!.user()!,
|
|
||||||
JSON.stringify({
|
|
||||||
mint: this.cashuWallet.mint.mintUrl,
|
|
||||||
proofs: this.proofs,
|
|
||||||
del: Array.from(this.eventIdToProofIdMap.keys()),
|
|
||||||
}),
|
|
||||||
'nip44',
|
|
||||||
)!;
|
|
||||||
await proofsEvent.sign();
|
|
||||||
await proofsEvent.publish();
|
|
||||||
this.recalculateBalance();
|
|
||||||
return sendResult.send;
|
|
||||||
}
|
|
||||||
|
|
||||||
async nutZap(amount: number, recipient: NDKUser, eventId?: NDKEventId, message?: string) {
|
|
||||||
const token = await this.createToken(amount, recipient);
|
|
||||||
const zapEvent = new NDKEvent(ndk);
|
|
||||||
zapEvent.kind = NDKKind.Nutzap;
|
|
||||||
zapEvent.content = message ?? '';
|
|
||||||
zapEvent.tags = [
|
|
||||||
['proof', JSON.stringify(token)],
|
|
||||||
['u', Wallet.mintUrl],
|
|
||||||
['p', recipient.pubkey],
|
|
||||||
];
|
|
||||||
if (eventId) zapEvent.tags.push(['e', eventId]);
|
|
||||||
await zapEvent.sign();
|
|
||||||
await zapEvent.publish();
|
|
||||||
|
|
||||||
const spendingHistoryEvent = new NDKEvent(ndk);
|
|
||||||
spendingHistoryEvent.kind = NDKKind.CashuWalletTx;
|
|
||||||
const historyContent = [
|
|
||||||
['direction', 'out'],
|
|
||||||
['amount', amount.toString()],
|
|
||||||
];
|
|
||||||
if (eventId) historyContent.push(['e', eventId]);
|
|
||||||
spendingHistoryEvent.content = await ndk.signer?.encrypt(
|
|
||||||
await ndk.signer!.user()!,
|
|
||||||
JSON.stringify(historyContent),
|
|
||||||
'nip44',
|
|
||||||
)!;
|
|
||||||
spendingHistoryEvent.tags = [
|
|
||||||
['e', zapEvent.id],
|
|
||||||
['p', recipient.pubkey],
|
|
||||||
];
|
|
||||||
await spendingHistoryEvent.sign();
|
|
||||||
await spendingHistoryEvent.publish();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export const wallet = new Wallet();
|
|
Loading…
Add table
Add a link
Reference in a new issue