Skip to content

Commit 14a469a

Browse files
committed
implement secret key wallet connector
1 parent d8cc73b commit 14a469a

File tree

5 files changed

+192
-10
lines changed

5 files changed

+192
-10
lines changed

packages/example/src/App.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ const App: React.FC = () => {
2727
/>
2828
),
2929
}}
30-
debugMode={false} // you may want to set this in REACT_APP_DEBUG_MODE
30+
debugMode={true} // you may want to set this in REACT_APP_DEBUG_MODE
3131
>
3232
<Body />
3333
</WalletKitProvider>

packages/walletkit/src/components/WalletSelectorModal/ButtonWithFooter.tsx

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -69,5 +69,10 @@ export const BigButton = styled.button`
6969
&:active {
7070
background: #363636;
7171
}
72+
transition: 0.2s ease;
7273
cursor: pointer;
74+
&:disabled {
75+
background: #aaa;
76+
cursor: not-allowed;
77+
}
7378
`;
Lines changed: 162 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,162 @@
1+
/* eslint-disable jsx-a11y/anchor-is-valid */
2+
import { css } from "@emotion/react";
3+
import styled from "@emotion/styled";
4+
import {
5+
DEFAULT_WALLET_PROVIDERS,
6+
DefaultWalletType,
7+
useSolana,
8+
} from "@saberhq/use-solana";
9+
import { Keypair } from "@solana/web3.js";
10+
import { useMemo, useState } from "react";
11+
12+
import { LabeledInput } from "../../LabeledInput";
13+
import { ButtonWithFooter } from "../ButtonWithFooter";
14+
15+
interface Props {
16+
onBack?: () => void;
17+
onError: (err: Error) => void;
18+
onSuccess?: () => void;
19+
}
20+
21+
export const WalletStepSecretKey: React.FC<Props> = ({
22+
onBack,
23+
onSuccess,
24+
onError,
25+
}: Props) => {
26+
const [keypairStr, setKeypairStr] = useState<string>(
27+
JSON.stringify([...Keypair.generate().secretKey])
28+
);
29+
const [keypair, keypairErr] = useMemo(() => {
30+
try {
31+
return [
32+
Keypair.fromSecretKey(
33+
Uint8Array.from(JSON.parse(keypairStr) as number[])
34+
),
35+
null,
36+
];
37+
} catch (e) {
38+
return [null, e as Error];
39+
}
40+
}, [keypairStr]);
41+
const { activate } = useSolana();
42+
43+
return (
44+
<Wrapper>
45+
<IconWrapper>
46+
<DEFAULT_WALLET_PROVIDERS.SecretKey.icon />
47+
</IconWrapper>
48+
<h2>Enter a JSON Keypair</h2>
49+
<p
50+
css={css`
51+
color: red !important;
52+
`}
53+
>
54+
Warning: do not use this outside of testing. If you were told to go here
55+
by someone else, don't do it.
56+
</p>
57+
<Fields>
58+
<LabeledInput
59+
label="Keypair (JSON)"
60+
placeholder="Enter a JSON keypair string"
61+
name="account"
62+
value={keypairStr}
63+
onChange={(e) => {
64+
setKeypairStr(e.target.value);
65+
}}
66+
/>
67+
{keypair && (
68+
<LabeledInput
69+
id="secretKeyConnectorPubkey"
70+
label="Public Key"
71+
name="publicKey"
72+
disabled
73+
value={keypair?.publicKey.toString()}
74+
/>
75+
)}
76+
</Fields>
77+
{keypairErr && (
78+
<p>
79+
<span
80+
css={css`
81+
color: red;
82+
`}
83+
>
84+
Error: {keypairErr.message}
85+
</span>
86+
</p>
87+
)}
88+
<ButtonWithFooter
89+
disabled={!keypair}
90+
// eslint-disable-next-line @typescript-eslint/no-misused-promises
91+
onClick={async () => {
92+
if (!keypair) {
93+
throw new Error("keypair missing");
94+
}
95+
try {
96+
await activate(DefaultWalletType.SecretKey, {
97+
secretKey: [...keypair.secretKey],
98+
});
99+
} catch (e) {
100+
onError?.(e as Error);
101+
return;
102+
}
103+
onSuccess?.();
104+
}}
105+
footer={
106+
<>
107+
Having trouble?{" "}
108+
<a
109+
href="#"
110+
onClick={(e) => {
111+
e.preventDefault();
112+
e.stopPropagation();
113+
onBack?.();
114+
}}
115+
>
116+
Go back
117+
</a>
118+
</>
119+
}
120+
>
121+
Continue
122+
</ButtonWithFooter>
123+
</Wrapper>
124+
);
125+
};
126+
127+
const IconWrapper = styled.div`
128+
& > svg,
129+
& > img {
130+
width: 36px;
131+
height: 36px;
132+
}
133+
margin-bottom: 32px;
134+
`;
135+
136+
const Wrapper = styled.div`
137+
padding: 28px;
138+
padding-top: 67px;
139+
140+
& > h2 {
141+
font-weight: bold;
142+
font-size: 20px;
143+
line-height: 25px;
144+
letter-spacing: -0.02em;
145+
color: #000000;
146+
}
147+
148+
& > p {
149+
font-weight: normal;
150+
font-size: 14px;
151+
line-height: 18px;
152+
letter-spacing: -0.02em;
153+
color: #696969;
154+
}
155+
`;
156+
157+
const Fields = styled.div`
158+
display: flex;
159+
flex-direction: column;
160+
gap: 8px;
161+
width: 100%;
162+
`;

packages/walletkit/src/components/WalletSelectorModal/WalletStepSelect/index.tsx

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -42,11 +42,14 @@ const getWalletProviders = (debugMode = false): readonly ProviderInfo[] => {
4242
([walletType, info]): ProviderInfo => ({
4343
type: walletType,
4444
info,
45-
mustInstall: !!(
46-
typeof window !== "undefined" &&
47-
info.isInstalled &&
48-
info.isInstalled()
49-
),
45+
mustInstall:
46+
walletType === DefaultWalletType.ReadOnly
47+
? !debugMode
48+
: !!(
49+
typeof window !== "undefined" &&
50+
info.isInstalled &&
51+
info.isInstalled()
52+
),
5053
})
5154
)
5255
.filter((p) =>

packages/walletkit/src/components/WalletSelectorModal/index.tsx

Lines changed: 16 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import { WalletStepIntro } from "./WalletStepIntro";
1111
import { DefaultAppIcon } from "./WalletStepIntro/DefaultAppIcon";
1212
import { WalletStepLedgerAdvanced } from "./WalletStepLedgerAdvanced";
1313
import { WalletStepRedirect } from "./WalletStepRedirect";
14+
import { WalletStepSecretKey } from "./WalletStepSecretKey";
1415
import type { ProviderInfo } from "./WalletStepSelect";
1516
import { WalletStepSelect } from "./WalletStepSelect";
1617

@@ -22,6 +23,7 @@ export enum ModalStep {
2223
Redirect = "redirect",
2324
Connecting = "connecting",
2425
LedgerAdvanced = "ledger-advanced",
26+
SecretKey = "secret-key",
2527
}
2628

2729
const defaultOnWalletKitError = (err: Error) => {
@@ -71,11 +73,8 @@ export const WalletSelectorModal: React.FC<Props> = ({
7173
setStep(ModalStep.Intro);
7274
break;
7375
case ModalStep.Redirect:
74-
setStep(ModalStep.Select);
75-
break;
7676
case ModalStep.Connecting:
77-
setStep(ModalStep.Select);
78-
break;
77+
case ModalStep.SecretKey:
7978
case ModalStep.LedgerAdvanced:
8079
setStep(ModalStep.Select);
8180
break;
@@ -106,6 +105,10 @@ export const WalletSelectorModal: React.FC<Props> = ({
106105
setStep(ModalStep.LedgerAdvanced);
107106
return;
108107
}
108+
if (info.type === DefaultWalletType.SecretKey) {
109+
setStep(ModalStep.SecretKey);
110+
return;
111+
}
109112

110113
setWalletToConnect(info);
111114
setStep(ModalStep.Connecting);
@@ -146,6 +149,15 @@ export const WalletSelectorModal: React.FC<Props> = ({
146149
onSuccess={onDismiss}
147150
/>
148151
)}
152+
{step === ModalStep.SecretKey && (
153+
<WalletStepSecretKey
154+
onBack={() => {
155+
setStep(ModalStep.Select);
156+
}}
157+
onError={onWalletKitError}
158+
onSuccess={onDismiss}
159+
/>
160+
)}
149161
</Modal>
150162
);
151163
};

0 commit comments

Comments
 (0)