-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
10 changed files
with
216 additions
and
11 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,86 @@ | ||
import { Keypair as SolanaKeypair } from '@solana/web3.js' | ||
import { atom, useAtomValue, useSetAtom } from 'jotai' | ||
import { atomWithStorage } from 'jotai/utils' | ||
import { createContext, ReactNode, useContext } from 'react' | ||
import { ellipsify } from '../account/account-ui' | ||
|
||
export interface Keypair { | ||
name: string | ||
publicKey: string | ||
secretKey: string | ||
active?: boolean | ||
solana?: SolanaKeypair | ||
} | ||
|
||
export const defaultKeypairs: Keypair[] = [] | ||
|
||
const keypairAtom = atomWithStorage<Keypair>('solana-keypair', defaultKeypairs[0]) | ||
const keypairsAtom = atomWithStorage<Keypair[]>('solana-keypairs', defaultKeypairs) | ||
|
||
const activeKeypairsAtom = atom<Keypair[]>((get) => { | ||
const keypairs = get(keypairsAtom) | ||
const keypair = get(keypairAtom) | ||
return keypairs.map((item) => ({ | ||
...item, | ||
active: item?.name === keypair?.name, | ||
})) | ||
}) | ||
|
||
const activeKeypairAtom = atom<Keypair>((get) => { | ||
const keypairs = get(activeKeypairsAtom) | ||
|
||
return keypairs.find((item) => item.active) || keypairs[0] | ||
}) | ||
|
||
export interface KeypairProviderContext { | ||
keypair: Keypair | ||
keypairs: Keypair[] | ||
addKeypair: (keypair: Keypair) => void | ||
deleteKeypair: (keypair: Keypair) => void | ||
setKeypair: (keypair: Keypair) => void | ||
generateKeypair: () => void | ||
} | ||
|
||
const Context = createContext<KeypairProviderContext>({} as KeypairProviderContext) | ||
|
||
export function KeypairProvider({ children }: { children: ReactNode }) { | ||
const keypair = useAtomValue(activeKeypairAtom) | ||
const keypairs = useAtomValue(activeKeypairsAtom) | ||
const setKeypair = useSetAtom(keypairAtom) | ||
const setKeypairs = useSetAtom(keypairsAtom) | ||
|
||
function addNewKeypair(kp: SolanaKeypair) { | ||
const keypair: Keypair = { | ||
name: ellipsify(kp.publicKey.toString()), | ||
publicKey: kp.publicKey.toString(), | ||
secretKey: `[${kp.secretKey.join(',')}]`, | ||
} | ||
setKeypairs([...keypairs, keypair]) | ||
if (!keypairs.length) { | ||
activateKeypair(keypair) | ||
} | ||
} | ||
|
||
function activateKeypair(keypair: Keypair) { | ||
const kp = SolanaKeypair.fromSecretKey(new Uint8Array(JSON.parse(keypair.secretKey))) | ||
setKeypair({ ...keypair, solana: kp }) | ||
} | ||
|
||
const value: KeypairProviderContext = { | ||
keypair, | ||
keypairs: keypairs.sort((a, b) => (a.name > b.name ? 1 : -1)), | ||
addKeypair: (keypair: Keypair) => { | ||
setKeypairs([...keypairs, keypair]) | ||
}, | ||
deleteKeypair: (keypair: Keypair) => { | ||
setKeypairs(keypairs.filter((item) => item.name !== keypair.name)) | ||
}, | ||
setKeypair: (keypair: Keypair) => activateKeypair(keypair), | ||
generateKeypair: () => addNewKeypair(SolanaKeypair.generate()), | ||
} | ||
return <Context.Provider value={value}>{children}</Context.Provider> | ||
} | ||
|
||
export function useKeypair() { | ||
return useContext(Context) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,24 @@ | ||
import { Button, Container, Group, Text, Title } from '@mantine/core' | ||
import { UiStack } from '@pubkey-ui/core' | ||
|
||
import { KeypairUiModal, KeypairUiTable } from './keypair-ui' | ||
import { useKeypair } from './keypair-data-access' | ||
|
||
export default function KeypairFeature() { | ||
const { generateKeypair } = useKeypair() | ||
return ( | ||
<Container py="xl" my="xl"> | ||
<UiStack gap="xl"> | ||
<UiStack align="center" gap="xl"> | ||
<Title order={2}>Keypairs</Title> | ||
<Text>Manage and select your Solana keypairs</Text> | ||
<Group> | ||
<Button onClick={generateKeypair}>Generate Keypair</Button> | ||
<KeypairUiModal /> | ||
</Group> | ||
</UiStack> | ||
<KeypairUiTable /> | ||
</UiStack> | ||
</Container> | ||
) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,86 @@ | ||
import { ActionIcon, Anchor, Button, Group, Modal, Table, Text, TextInput } from '@mantine/core' | ||
import { useDisclosure } from '@mantine/hooks' | ||
import { IconCurrencySolana, IconTrash } from '@tabler/icons-react' | ||
import { useState } from 'react' | ||
import { useKeypair } from './keypair-data-access' | ||
import { UiAlert, UiDebugModal } from '@pubkey-ui/core' | ||
|
||
export function KeypairUiModal() { | ||
const { addKeypair } = useKeypair() | ||
const [opened, { close, open }] = useDisclosure(false) | ||
const [name, setName] = useState('') | ||
|
||
return ( | ||
<> | ||
<Button onClick={open}>Add Keypair</Button> | ||
<Modal opened={opened} onClose={close} title="Add Keypair"> | ||
<TextInput type="text" placeholder="Name" value={name} onChange={(e) => setName(e.target.value)} /> | ||
|
||
<Button | ||
onClick={() => { | ||
addKeypair({ name, publicKey: '', secretKey: '' }) | ||
close() | ||
}} | ||
> | ||
Save | ||
</Button> | ||
</Modal> | ||
</> | ||
) | ||
} | ||
|
||
export function KeypairUiTable() { | ||
const { keypairs, generateKeypair, setKeypair, deleteKeypair } = useKeypair() | ||
|
||
return keypairs.length ? ( | ||
<Table> | ||
<Table.Thead> | ||
<Table.Tr> | ||
<Table.Th>Name / Network / Endpoint</Table.Th> | ||
<Table.Th align="center">Actions</Table.Th> | ||
</Table.Tr> | ||
</Table.Thead> | ||
<Table.Tbody> | ||
{keypairs?.map((item) => ( | ||
<Table.Tr key={item.name}> | ||
<Table.Td> | ||
<Text size="lg"> | ||
{item?.active ? ( | ||
item.name | ||
) : ( | ||
<Anchor component="button" title="Select keypair" onClick={() => setKeypair(item)}> | ||
{item.name} | ||
</Anchor> | ||
)} | ||
</Text> | ||
<Text c="dimmed" size="xs"> | ||
{item.publicKey} | ||
</Text> | ||
</Table.Td> | ||
<Table.Td> | ||
<Group gap="xs"> | ||
<ActionIcon disabled={!item.solana} size="sm" variant="light"> | ||
<IconCurrencySolana /> | ||
</ActionIcon> | ||
<UiDebugModal data={item} /> | ||
<ActionIcon | ||
size="sm" | ||
variant="light" | ||
disabled={item.active} | ||
onClick={() => { | ||
if (!window.confirm('Are you sure?')) return | ||
deleteKeypair(item) | ||
}} | ||
> | ||
<IconTrash size={16} /> | ||
</ActionIcon> | ||
</Group> | ||
</Table.Td> | ||
</Table.Tr> | ||
))} | ||
</Table.Tbody> | ||
</Table> | ||
) : ( | ||
<UiAlert title="No keypairs found" message={<Button onClick={() => generateKeypair()}>Generate Keypair</Button>} /> | ||
) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters