feat: finish alby wallet

This commit is contained in:
2024-01-04 12:05:13 +00:00
parent 0442c3512c
commit 9f88b44b91
16 changed files with 341 additions and 109 deletions

View File

@ -105,7 +105,11 @@ export default function WalletPage(props: { showHistory: boolean }) {
<div>
<select className="w-max" onChange={e => Wallets.switch(e.target.value)} value={walletState.config?.id}>
{Wallets.list().map(a => {
return <option value={a.id} key={a.id}>{a.info.alias}</option>;
return (
<option value={a.id} key={a.id}>
{a.info.alias}
</option>
);
})}
</select>
</div>

View File

@ -10,16 +10,26 @@ import AlbyIcon from "@/Icons/Alby";
import Icon from "@/Icons/Icon";
import { getAlbyOAuth } from "./wallet/Alby";
const WalletRow = (props: { logo: ReactNode; name: ReactNode; url: string; desc?: ReactNode }) => {
const WalletRow = (props: {
logo: ReactNode;
name: ReactNode;
url: string;
desc?: ReactNode;
onClick?: () => void;
}) => {
const navigate = useNavigate();
return (
<div
className="flex items-center gap-4 px-4 py-2 bg-[--gray-superdark] rounded-xl hover:bg-[--gray-ultradark]"
onClick={() => {
if (props.url.startsWith("http")) {
window.location.href = props.url;
if (props.onClick) {
props.onClick();
} else {
navigate(props.url);
if (props.url.startsWith("http")) {
window.location.href = props.url;
} else {
navigate(props.url);
}
}
}}>
<div className="rounded-xl aspect-square h-[4rem] bg-[--gray-dark] p-3 flex items-center justify-center">
@ -35,7 +45,6 @@ const WalletRow = (props: { logo: ReactNode; name: ReactNode; url: string; desc?
};
const WalletSettings = () => {
const alby = getAlbyOAuth();
return (
<>
<h3>
@ -68,12 +77,18 @@ const WalletSettings = () => {
url="/settings/wallet/cashu"
desc={<FormattedMessage defaultMessage="Cashu mint wallet" id="3natuV" />}
/>
<WalletRow
logo={<AlbyIcon size={64} />}
name="Alby"
url={alby.authUrl}
desc={<FormattedMessage defaultMessage="Alby wallet connection" id="XPB8VV" />}
/>
{CONFIG.alby && (
<WalletRow
logo={<AlbyIcon size={64} />}
name="Alby"
url={""}
onClick={() => {
const alby = getAlbyOAuth();
window.location.href = alby.getAuthUrl();
}}
desc={<FormattedMessage defaultMessage="Alby wallet connection" id="XPB8VV" />}
/>
)}
</div>
</>
);

View File

@ -1,18 +1,40 @@
import PageSpinner from "@/Element/PageSpinner";
import { WalletConfig, WalletKind, Wallets } from "@/Wallet";
import AlbyWallet from "@/Wallet/AlbyWallet";
import { sha256 } from "@noble/hashes/sha256";
import { randomBytes } from "@noble/hashes/utils";
import { base64, base64urlnopad, hex } from "@scure/base";
import { unixNow } from "@snort/shared";
import { useEffect, useState } from "react";
import { useLocation } from "react-router-dom";
import { useLocation, useNavigate } from "react-router-dom";
import { v4 as uuid } from "uuid";
export default function AlbyOAuth() {
const navigate = useNavigate();
const location = useLocation();
const alby = getAlbyOAuth();
const [error, setError] = useState("");
async function setupWallet(token: string) {
const auth = await alby.getToken(token);
console.debug(auth);
try {
const auth = await alby.getToken(token);
console.debug(auth);
const connection = new AlbyWallet(auth, () => {});
const info = await connection.getInfo();
const newWallet = {
id: uuid(),
kind: WalletKind.Alby,
active: true,
info,
data: JSON.stringify(auth),
} as WalletConfig;
Wallets.add(newWallet);
navigate("/settings/wallet");
} catch (e) {
setError((e as Error).message);
}
}
useEffect(() => {
@ -38,29 +60,30 @@ export default function AlbyOAuth() {
}
export function getAlbyOAuth() {
const clientId = "35EQp6crss";
const clientSecret = "DTUPIqOjsjwxZXcJwF5C";
const clientId = CONFIG.alby?.clientId ?? "";
const clientSecret = CONFIG.alby?.clientSecret ?? "";
const redirectUrl = `${window.location.protocol}//${window.location.host}/settings/wallet/alby`;
const scopes = ["invoices:create", "invoices:read", "transactions:read", "balance:read", "payments:send"];
const ec = new TextEncoder();
const code_verifier = hex.encode(randomBytes(64));
window.sessionStorage.setItem("alby-code", code_verifier);
const params = new URLSearchParams();
params.set("client_id", clientId);
params.set("response_type", "code");
params.set("code_challenge", base64urlnopad.encode(sha256(code_verifier)));
params.set("code_challenge_method", "S256");
params.set("redirect_uri", redirectUrl);
params.set("scope", scopes.join(" "));
const tokenUrl = "https://api.getalby.com/oauth/token";
const authUrl = `https://getalby.com/oauth?${params}`;
return {
tokenUrl,
authUrl,
getAuthUrl: () => {
const code_verifier = hex.encode(randomBytes(64));
window.sessionStorage.setItem("alby-code", code_verifier);
const params = new URLSearchParams();
params.set("client_id", clientId);
params.set("response_type", "code");
params.set("code_challenge", base64urlnopad.encode(sha256(code_verifier)));
params.set("code_challenge_method", "S256");
params.set("redirect_uri", redirectUrl);
params.set("scope", scopes.join(" "));
return `https://getalby.com/oauth?${params}`;
},
getToken: async (token: string) => {
const code = window.sessionStorage.getItem("alby-code");
if (!code) throw new Error("Alby code is missing!");
@ -85,10 +108,19 @@ export function getAlbyOAuth() {
const data = await req.json();
if (req.ok) {
return data.access_token as string;
return { ...data, created_at: unixNow() } as OAuthToken;
} else {
throw new Error(data.error_description as string);
}
},
};
}
export interface OAuthToken {
access_token: string;
created_at: number;
expires_in: number;
refresh_token: string;
scope: string;
token_type: string;
}

View File

@ -16,7 +16,7 @@ const ConnectLNDHub = () => {
async function tryConnect(config: string) {
try {
const connection = new LNDHubWallet(config);
const connection = new LNDHubWallet(config, () => {});
await connection.login();
const info = await connection.getInfo();

View File

@ -16,7 +16,7 @@ const ConnectNostrWallet = () => {
async function tryConnect(config: string) {
try {
const connection = new NostrConnectWallet(config);
const connection = new NostrConnectWallet(config, () => {});
await connection.login();
const info = await connection.getInfo();