Skip to content

Commit

Permalink
Merge pull request #101 from xch-dev/walletconnect-update
Browse files Browse the repository at this point in the history
WalletConnect integration
  • Loading branch information
Rigidity authored Nov 19, 2024
2 parents 90a86ec + 7fe4b17 commit 97f2171
Show file tree
Hide file tree
Showing 13 changed files with 2,045 additions and 57 deletions.
2 changes: 2 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,8 @@
"@tauri-apps/api": "2.0.0-rc.4",
"@tauri-apps/plugin-clipboard-manager": "2.0.0-rc.1",
"@tauri-apps/plugin-shell": "2.0.0",
"@walletconnect/sign-client": "^2.17.2",
"@walletconnect/types": "^2.17.2",
"bignumber.js": "^9.1.2",
"class-variance-authority": "^0.7.0",
"clsx": "^2.1.1",
Expand Down
1,434 changes: 1,434 additions & 0 deletions pnpm-lock.yaml

Large diffs are not rendered by default.

1 change: 1 addition & 0 deletions src-tauri/capabilities/default.json
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
"core:image:default",
"core:menu:default",
"core:tray:default",
"core:window:allow-request-user-attention",
"clipboard-manager:default",
"clipboard-manager:allow-write-text",
"shell:default"
Expand Down
49 changes: 16 additions & 33 deletions src/App.tsx
Original file line number Diff line number Diff line change
@@ -1,15 +1,16 @@
import { createContext, useEffect, useMemo, useState } from 'react';
import { createContext, useEffect, useMemo } from 'react';
import {
createHashRouter,
createRoutesFromElements,
Route,
RouterProvider,
} from 'react-router-dom';
import { useLocalStorage } from 'usehooks-ts';
import { commands } from './bindings';
import Container from './components/Container';
import { PeerProvider } from './contexts/PeerContext';
import { PriceProvider } from './contexts/PriceContext';
import { WalletConnectProvider } from './contexts/WalletConnectContext';
import useInitialization from './hooks/useInitialization';
import { useWallet } from './hooks/useWallet';
import Collection from './pages/Collection';
import CreateProfile from './pages/CreateProfile';
import CreateWallet from './pages/CreateWallet';
Expand Down Expand Up @@ -87,9 +88,6 @@ const router = createHashRouter(
export default function App() {
const [dark, setDark] = useLocalStorage('dark', false);

const [initialized, setInitialized] = useState(false);
const [error, setError] = useState<string | null>(null);

const darkMode = useMemo<DarkModeContext>(
() => ({
toggle: () => setDark((dark) => !dark),
Expand All @@ -105,41 +103,26 @@ export default function App() {
root.classList.add(dark ? 'dark' : 'light');
}, [dark]);

useEffect(() => {
commands
.initialize()
.then((result) => {
if (result.status === 'ok') {
setInitialized(true);
const initialized = useInitialization();
const wallet = useWallet(initialized);

commands.getKey({}).then((result) => {
if (result.status === 'ok' && result.data.key !== null) {
fetchState();
}
});
} else {
console.error(result.error);
setError(result.error.reason);
}
})
.catch(setError);
}, []);
useEffect(() => {
if (wallet !== null) {
fetchState();
}
}, [wallet]);

return (
<DarkModeContext.Provider value={darkMode}>
{initialized && (
<PeerProvider>
<PriceProvider>
<RouterProvider router={router} />
</PriceProvider>
<WalletConnectProvider>
<PriceProvider>
<RouterProvider router={router} />
</PriceProvider>
</WalletConnectProvider>
</PeerProvider>
)}
{error && (
<Container>
<h2 className='text-2xl font-bold mb-2'>Error</h2>
<p className='text-red-500 mt-2'>{error}</p>
</Container>
)}
</DarkModeContext.Provider>
);
}
4 changes: 3 additions & 1 deletion src/components/Header.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import { Link, useLocation, useNavigate } from 'react-router-dom';
import { Nav } from './Nav';
import { Button } from './ui/button';
import { Sheet, SheetContent, SheetTrigger } from './ui/sheet';
import useInitialization from '@/hooks/useInitialization';

export default function Header(
props: PropsWithChildren<{
Expand All @@ -19,7 +20,8 @@ export default function Header(
const navigate = useNavigate();
const location = useLocation();

const { wallet } = useWallet();
const initialized = useInitialization();
const wallet = useWallet(initialized);
const { peers } = usePeers();

const walletState = useWalletState();
Expand Down
19 changes: 6 additions & 13 deletions src/components/Layout.tsx
Original file line number Diff line number Diff line change
@@ -1,17 +1,17 @@
import { Cog, LogOut } from 'lucide-react';
import { PropsWithChildren, useEffect, useMemo, useState } from 'react';
import { PropsWithChildren, useMemo } from 'react';
import { Link, useNavigate } from 'react-router-dom';
import { commands, KeyInfo } from '../bindings';

import { usePeers } from '@/contexts/PeerContext';
import useInitialization from '@/hooks/useInitialization';
import { useWallet } from '@/hooks/useWallet';
import icon from '@/icon.png';
import { logoutAndUpdateState, useWalletState } from '@/state';
import { Nav } from './Nav';

export default function Layout(props: PropsWithChildren<object>) {
const navigate = useNavigate();

const [key, setKey] = useState<KeyInfo | null>(null);
const { peers } = usePeers();

const walletState = useWalletState();
Expand All @@ -25,15 +25,8 @@ export default function Layout(props: PropsWithChildren<object>) {
return Math.max(max, peer.peak_height);
}, 0) || 0;

useEffect(() => {
commands.getKey({}).then((result) => {
if (result.status === 'error') {
return;
}
if (result.data) return setKey(result.data.key);
navigate('/');
});
}, [navigate]);
const initialized = useInitialization();
const wallet = useWallet(initialized);

const logout = () => {
logoutAndUpdateState().then(() => {
Expand All @@ -51,7 +44,7 @@ export default function Layout(props: PropsWithChildren<object>) {
className='flex items-center gap-2 font-semibold'
>
<img src={icon} className='h-6 w-6' />
{key?.name}
{wallet?.name}
</Link>
</div>
<div className='flex-1 flex flex-col justify-between pb-4'>
Expand Down
122 changes: 122 additions & 0 deletions src/constants/walletConnectCommands.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
import { z } from 'zod';

const coinType = z.object({
parent_coin_info: z.string(),
puzzle_hash: z.string(),
amount: z.number(),
});

const coinSpendType = z.object({
coin: coinType,
puzzle_reveal: z.string(),
solution: z.string(),
});

const spendBundleType = z.object({
coin_spends: z.array(coinSpendType),
aggregated_signature: z.string(),
});

enum MempoolInclusionStatus {
SUCCESS = 1, // Transaction added to mempool
PENDING = 2, // Transaction not yet added to mempool
FAILED = 3, // Transaction was invalid and dropped
}

// Convert the array into an object keyed by the `command`
export const walletConnectCommands = {
chip0002_getPublicKeys: {
requiresConfirmation: false,
paramsType: z
.object({
limit: z.number().optional(),
offset: z.number().optional(),
})
.optional(),
returnType: z.array(z.string()),
},
chip0002_filterUnlockedCoins: {
requiresConfirmation: false,
paramsType: z.object({ coinNames: z.array(z.string()).min(1) }),
returnType: z.array(z.string()),
},
chip0002_getAssetCoins: {
requiresConfirmation: false,
paramsType: z.object({
type: z.string().nullable(),
assetId: z.string().nullable(),
includedLocked: z.boolean().optional(),
offset: z.number().optional(),
limit: z.number().optional(),
}),
returnType: z.array(
z.object({
coin: coinType,
coinName: z.string(),
puzzle: z.string(),
confirmedBlockIndex: z.number(),
locked: z.boolean(),
lineageProof: z
.object({
parentName: z.string().nullable(),
innerPuzzleHash: z.string().nullable(),
amount: z.number().nullable(),
})
.nullable(),
}),
),
},
chip0002_getAssetBalance: {
requiresConfirmation: false,
paramsType: z.object({
type: z.string().nullable(),
assetId: z.string().nullable(),
}),
returnType: z.object({
confirmed: z.string(),
spendable: z.string(),
spendableCoinCount: z.number(),
}),
},
chip0002_signCoinSpends: {
requiresConfirmation: true,
paramsType: z.object({
coinSpends: z.array(coinType),
partialSign: z.boolean().optional(),
}),
returnType: z.string(),
},
chip0002_signMessage: {
requiresConfirmation: true,
paramsType: z.object({
message: z.string(),
publicKey: z.string(),
}),
returnType: z.string(),
},
chip0002_sendTransaction: {
requiresConfirmation: false,
paramsType: z.object({ spendBundle: spendBundleType }),
returnType: z.object({
status: z
.number()
.refine((val) => Object.values(MempoolInclusionStatus).includes(val)),
error: z.string().nullable(),
}),
},
} as const;

// Define a union of valid commands
export type WalletConnectCommand = keyof typeof walletConnectCommands;

type CommandConfig<C extends WalletConnectCommand> =
(typeof walletConnectCommands)[C];

// Function to parse params based on the command
export const parseCommand = <C extends WalletConnectCommand>(
command: C,
params: unknown,
): z.infer<CommandConfig<C>['paramsType']> => {
const commandInfo = walletConnectCommands[command];
return commandInfo.paramsType.parse(params);
};
Loading

0 comments on commit 97f2171

Please sign in to comment.