feat: finish alby wallet
This commit is contained in:
@ -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>
|
||||
|
@ -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>
|
||||
</>
|
||||
);
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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();
|
||||
|
||||
|
@ -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();
|
||||
|
||||
|
Reference in New Issue
Block a user