@iamgame/wallet-sdk 0.1.0 → 0.1.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.cjs +219 -2
- package/dist/index.d.cts +35 -3
- package/dist/index.d.ts +35 -3
- package/dist/index.js +220 -3
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -80,6 +80,23 @@ var SolvenClient = class {
|
|
|
80
80
|
this.opts.storage.set(session);
|
|
81
81
|
return session;
|
|
82
82
|
}
|
|
83
|
+
/** Email + OTP: send a one-time code to the address. */
|
|
84
|
+
async initiateEmail(email) {
|
|
85
|
+
return this.publicCall(
|
|
86
|
+
"POST",
|
|
87
|
+
"/auth/email/initiate",
|
|
88
|
+
{ email }
|
|
89
|
+
);
|
|
90
|
+
}
|
|
91
|
+
/** Email + OTP: exchange the code for a session. */
|
|
92
|
+
async verifyEmail(email, code) {
|
|
93
|
+
const session = await this.publicCall("POST", "/auth/email/verify", {
|
|
94
|
+
email,
|
|
95
|
+
code
|
|
96
|
+
});
|
|
97
|
+
this.opts.storage.set(session);
|
|
98
|
+
return session;
|
|
99
|
+
}
|
|
83
100
|
async refreshSession() {
|
|
84
101
|
const current = this.opts.storage.get();
|
|
85
102
|
if (!current) return null;
|
|
@@ -254,7 +271,7 @@ function useSolvenContext() {
|
|
|
254
271
|
if (!ctx) throw new Error("useSolvenContext must be inside a <SolvenProvider />");
|
|
255
272
|
return ctx;
|
|
256
273
|
}
|
|
257
|
-
function SolvenProvider({ children, ...opts }) {
|
|
274
|
+
function SolvenProvider({ children, autoTelegram = true, ...opts }) {
|
|
258
275
|
const client = react.useMemo(() => new SolvenClient(opts), [
|
|
259
276
|
opts.publishableKey,
|
|
260
277
|
opts.baseUrl,
|
|
@@ -295,6 +312,32 @@ function SolvenProvider({ children, ...opts }) {
|
|
|
295
312
|
cancelled = true;
|
|
296
313
|
};
|
|
297
314
|
}, [client]);
|
|
315
|
+
const triedTelegram = react.useRef(false);
|
|
316
|
+
react.useEffect(() => {
|
|
317
|
+
if (!autoTelegram || status !== "anonymous" || triedTelegram.current) return;
|
|
318
|
+
triedTelegram.current = true;
|
|
319
|
+
let cancelled = false;
|
|
320
|
+
(async () => {
|
|
321
|
+
let initData = getTelegramInitData();
|
|
322
|
+
for (let i = 0; i < 4 && !initData; i++) {
|
|
323
|
+
await new Promise((r) => setTimeout(r, 400));
|
|
324
|
+
if (cancelled) return;
|
|
325
|
+
initData = getTelegramInitData();
|
|
326
|
+
}
|
|
327
|
+
if (!initData) return;
|
|
328
|
+
try {
|
|
329
|
+
notifyTelegramReady();
|
|
330
|
+
const next = await client.verifyTelegram({ initData });
|
|
331
|
+
if (cancelled) return;
|
|
332
|
+
setSession(next);
|
|
333
|
+
setStatus("authenticated");
|
|
334
|
+
} catch {
|
|
335
|
+
}
|
|
336
|
+
})();
|
|
337
|
+
return () => {
|
|
338
|
+
cancelled = true;
|
|
339
|
+
};
|
|
340
|
+
}, [autoTelegram, status, client]);
|
|
298
341
|
const value = {
|
|
299
342
|
client,
|
|
300
343
|
session,
|
|
@@ -327,6 +370,20 @@ function useSolvenAuth() {
|
|
|
327
370
|
const session2 = await client.verifyTelegram({ initData });
|
|
328
371
|
setSession(session2);
|
|
329
372
|
}, [client, setSession]);
|
|
373
|
+
const requestEmailOtp = react.useCallback(
|
|
374
|
+
async (email) => {
|
|
375
|
+
const { expiresAt } = await client.initiateEmail(email);
|
|
376
|
+
return { expiresAt };
|
|
377
|
+
},
|
|
378
|
+
[client]
|
|
379
|
+
);
|
|
380
|
+
const connectEmail = react.useCallback(
|
|
381
|
+
async (email, code) => {
|
|
382
|
+
const session2 = await client.verifyEmail(email, code);
|
|
383
|
+
setSession(session2);
|
|
384
|
+
},
|
|
385
|
+
[client, setSession]
|
|
386
|
+
);
|
|
330
387
|
const logout = react.useCallback(async () => {
|
|
331
388
|
await client.logout();
|
|
332
389
|
setSession(null);
|
|
@@ -336,6 +393,8 @@ function useSolvenAuth() {
|
|
|
336
393
|
status,
|
|
337
394
|
connectExternal,
|
|
338
395
|
connectTelegram,
|
|
396
|
+
requestEmailOtp,
|
|
397
|
+
connectEmail,
|
|
339
398
|
logout
|
|
340
399
|
};
|
|
341
400
|
}
|
|
@@ -527,9 +586,13 @@ var defaultTheme = {
|
|
|
527
586
|
fontFamily: 'system-ui, -apple-system, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif'
|
|
528
587
|
};
|
|
529
588
|
function SolvenLoginContent(props) {
|
|
530
|
-
const { connectExternal, connectTelegram, status, user } = useSolvenAuth();
|
|
589
|
+
const { connectExternal, connectTelegram, requestEmailOtp, connectEmail, status, user } = useSolvenAuth();
|
|
531
590
|
const [busy, setBusy] = react.useState(null);
|
|
532
591
|
const [error, setError] = react.useState(null);
|
|
592
|
+
const [email, setEmail] = react.useState("");
|
|
593
|
+
const [otp, setOtp] = react.useState("");
|
|
594
|
+
const [otpSent, setOtpSent] = react.useState(false);
|
|
595
|
+
const showEmail = props.showEmail ?? true;
|
|
533
596
|
const theme = react.useMemo(
|
|
534
597
|
() => ({ ...defaultTheme, ...props.theme ?? {} }),
|
|
535
598
|
[props.theme]
|
|
@@ -596,6 +659,31 @@ function SolvenLoginContent(props) {
|
|
|
596
659
|
setBusy(null);
|
|
597
660
|
}
|
|
598
661
|
};
|
|
662
|
+
const handleSendOtp = async () => {
|
|
663
|
+
setBusy("email");
|
|
664
|
+
setError(null);
|
|
665
|
+
try {
|
|
666
|
+
await requestEmailOtp(email.trim());
|
|
667
|
+
setOtpSent(true);
|
|
668
|
+
} catch (e) {
|
|
669
|
+
setError(e instanceof Error ? e.message : "Couldn't send the code");
|
|
670
|
+
} finally {
|
|
671
|
+
setBusy(null);
|
|
672
|
+
}
|
|
673
|
+
};
|
|
674
|
+
const handleVerifyOtp = async () => {
|
|
675
|
+
setBusy("email");
|
|
676
|
+
setError(null);
|
|
677
|
+
try {
|
|
678
|
+
await connectEmail(email.trim(), otp.trim());
|
|
679
|
+
props.onSignIn?.();
|
|
680
|
+
props.onCloseAfterSignIn?.();
|
|
681
|
+
} catch (e) {
|
|
682
|
+
setError(e instanceof Error ? e.message : "Couldn't verify the code");
|
|
683
|
+
} finally {
|
|
684
|
+
setBusy(null);
|
|
685
|
+
}
|
|
686
|
+
};
|
|
599
687
|
const buttonBase = {
|
|
600
688
|
width: "100%",
|
|
601
689
|
padding: "0.75rem 0.875rem",
|
|
@@ -681,6 +769,135 @@ function SolvenLoginContent(props) {
|
|
|
681
769
|
w.id
|
|
682
770
|
);
|
|
683
771
|
}) }),
|
|
772
|
+
!inTma && showEmail && /* @__PURE__ */ jsxRuntime.jsxs("div", { style: { marginTop: "1rem" }, children: [
|
|
773
|
+
/* @__PURE__ */ jsxRuntime.jsxs(
|
|
774
|
+
"div",
|
|
775
|
+
{
|
|
776
|
+
style: {
|
|
777
|
+
display: "flex",
|
|
778
|
+
alignItems: "center",
|
|
779
|
+
gap: "0.625rem",
|
|
780
|
+
margin: "0.25rem 0 0.875rem",
|
|
781
|
+
color: theme.muted,
|
|
782
|
+
fontSize: "0.8125rem"
|
|
783
|
+
},
|
|
784
|
+
children: [
|
|
785
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { style: { flex: 1, height: 1, background: theme.border } }),
|
|
786
|
+
"or",
|
|
787
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { style: { flex: 1, height: 1, background: theme.border } })
|
|
788
|
+
]
|
|
789
|
+
}
|
|
790
|
+
),
|
|
791
|
+
!otpSent ? /* @__PURE__ */ jsxRuntime.jsxs("div", { style: { display: "flex", flexDirection: "column", gap: "0.5rem" }, children: [
|
|
792
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
793
|
+
"input",
|
|
794
|
+
{
|
|
795
|
+
type: "email",
|
|
796
|
+
inputMode: "email",
|
|
797
|
+
autoComplete: "email",
|
|
798
|
+
placeholder: "you@email.com",
|
|
799
|
+
value: email,
|
|
800
|
+
onChange: (e) => setEmail(e.target.value),
|
|
801
|
+
style: {
|
|
802
|
+
width: "100%",
|
|
803
|
+
padding: "0.75rem 0.875rem",
|
|
804
|
+
borderRadius: theme.radius,
|
|
805
|
+
border: `1px solid ${theme.border}`,
|
|
806
|
+
background: theme.surface,
|
|
807
|
+
color: theme.foreground,
|
|
808
|
+
fontSize: "0.9375rem",
|
|
809
|
+
outline: "none",
|
|
810
|
+
boxSizing: "border-box"
|
|
811
|
+
}
|
|
812
|
+
}
|
|
813
|
+
),
|
|
814
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
815
|
+
"button",
|
|
816
|
+
{
|
|
817
|
+
type: "button",
|
|
818
|
+
onClick: handleSendOtp,
|
|
819
|
+
disabled: busy !== null || !email.includes("@"),
|
|
820
|
+
style: {
|
|
821
|
+
...buttonBase,
|
|
822
|
+
justifyContent: "center",
|
|
823
|
+
background: theme.primary,
|
|
824
|
+
color: "#ffffff",
|
|
825
|
+
border: "none",
|
|
826
|
+
opacity: busy === "email" || !email.includes("@") ? 0.6 : 1
|
|
827
|
+
},
|
|
828
|
+
children: busy === "email" ? "Sending\u2026" : "Email me a code"
|
|
829
|
+
}
|
|
830
|
+
)
|
|
831
|
+
] }) : /* @__PURE__ */ jsxRuntime.jsxs("div", { style: { display: "flex", flexDirection: "column", gap: "0.5rem" }, children: [
|
|
832
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { style: { color: theme.muted, fontSize: "0.8125rem" }, children: [
|
|
833
|
+
"Enter the 6-digit code sent to ",
|
|
834
|
+
/* @__PURE__ */ jsxRuntime.jsx("strong", { children: email.trim() }),
|
|
835
|
+
"."
|
|
836
|
+
] }),
|
|
837
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
838
|
+
"input",
|
|
839
|
+
{
|
|
840
|
+
type: "text",
|
|
841
|
+
inputMode: "numeric",
|
|
842
|
+
autoComplete: "one-time-code",
|
|
843
|
+
maxLength: 6,
|
|
844
|
+
placeholder: "123456",
|
|
845
|
+
value: otp,
|
|
846
|
+
onChange: (e) => setOtp(e.target.value.replace(/\D/g, "")),
|
|
847
|
+
style: {
|
|
848
|
+
width: "100%",
|
|
849
|
+
padding: "0.75rem 0.875rem",
|
|
850
|
+
borderRadius: theme.radius,
|
|
851
|
+
border: `1px solid ${theme.border}`,
|
|
852
|
+
background: theme.surface,
|
|
853
|
+
color: theme.foreground,
|
|
854
|
+
fontSize: "1.125rem",
|
|
855
|
+
letterSpacing: "0.3em",
|
|
856
|
+
textAlign: "center",
|
|
857
|
+
outline: "none",
|
|
858
|
+
boxSizing: "border-box"
|
|
859
|
+
}
|
|
860
|
+
}
|
|
861
|
+
),
|
|
862
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
863
|
+
"button",
|
|
864
|
+
{
|
|
865
|
+
type: "button",
|
|
866
|
+
onClick: handleVerifyOtp,
|
|
867
|
+
disabled: busy !== null || otp.length !== 6,
|
|
868
|
+
style: {
|
|
869
|
+
...buttonBase,
|
|
870
|
+
justifyContent: "center",
|
|
871
|
+
background: theme.primary,
|
|
872
|
+
color: "#ffffff",
|
|
873
|
+
border: "none",
|
|
874
|
+
opacity: busy === "email" || otp.length !== 6 ? 0.6 : 1
|
|
875
|
+
},
|
|
876
|
+
children: busy === "email" ? "Verifying\u2026" : "Verify & sign in"
|
|
877
|
+
}
|
|
878
|
+
),
|
|
879
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
880
|
+
"button",
|
|
881
|
+
{
|
|
882
|
+
type: "button",
|
|
883
|
+
onClick: () => {
|
|
884
|
+
setOtpSent(false);
|
|
885
|
+
setOtp("");
|
|
886
|
+
setError(null);
|
|
887
|
+
},
|
|
888
|
+
style: {
|
|
889
|
+
background: "none",
|
|
890
|
+
border: "none",
|
|
891
|
+
color: theme.muted,
|
|
892
|
+
fontSize: "0.8125rem",
|
|
893
|
+
cursor: "pointer",
|
|
894
|
+
padding: "0.25rem"
|
|
895
|
+
},
|
|
896
|
+
children: "Use a different email"
|
|
897
|
+
}
|
|
898
|
+
)
|
|
899
|
+
] })
|
|
900
|
+
] }),
|
|
684
901
|
error && /* @__PURE__ */ jsxRuntime.jsx(
|
|
685
902
|
"div",
|
|
686
903
|
{
|
package/dist/index.d.cts
CHANGED
|
@@ -1,12 +1,23 @@
|
|
|
1
1
|
import * as react_jsx_runtime from 'react/jsx-runtime';
|
|
2
2
|
import React$1 from 'react';
|
|
3
3
|
|
|
4
|
-
type AuthMethod = "siws" | "telegram";
|
|
4
|
+
type AuthMethod = "siws" | "telegram" | "email";
|
|
5
5
|
interface IUserIdentity {
|
|
6
6
|
id: string;
|
|
7
7
|
type: AuthMethod;
|
|
8
|
-
/** For SIWS: base58 Solana pubkey. For Telegram: numeric user id as string. */
|
|
8
|
+
/** For SIWS: base58 Solana pubkey. For Telegram: numeric user id as string. For email: the address. */
|
|
9
9
|
externalId: string;
|
|
10
|
+
/** Provider-specific profile captured at login (e.g. Telegram { username, firstName }).
|
|
11
|
+
* Display/attribution only — null when none was captured. */
|
|
12
|
+
profile?: Record<string, unknown> | null;
|
|
13
|
+
}
|
|
14
|
+
interface IUserWallet {
|
|
15
|
+
/** base58 Solana pubkey */
|
|
16
|
+
address: string;
|
|
17
|
+
/** test (devnet) or live (mainnet) — wallets are isolated per environment. */
|
|
18
|
+
environment: "test" | "live";
|
|
19
|
+
custody: "self" | "operator";
|
|
20
|
+
status: "active" | "exported" | "archived";
|
|
10
21
|
}
|
|
11
22
|
interface IUser {
|
|
12
23
|
id: string;
|
|
@@ -14,6 +25,8 @@ interface IUser {
|
|
|
14
25
|
studioUserId?: string;
|
|
15
26
|
primaryIdentity: IUserIdentity;
|
|
16
27
|
identities: IUserIdentity[];
|
|
28
|
+
/** Managed wallets owned by this user. Lets a relying server confirm a claimed address. */
|
|
29
|
+
wallets: IUserWallet[];
|
|
17
30
|
createdAt: string;
|
|
18
31
|
}
|
|
19
32
|
/** SIWS challenge issued by the signer; the client signs it with its external wallet. */
|
|
@@ -146,7 +159,7 @@ interface IWebhookSignatureHeader {
|
|
|
146
159
|
signature: string;
|
|
147
160
|
}
|
|
148
161
|
|
|
149
|
-
type IAMGameErrorCode = "auth/invalid_signature" | "auth/challenge_expired" | "auth/challenge_already_redeemed" | "auth/invalid_init_data" | "auth/init_data_stale" | "auth/init_data_replayed" | "auth/missing_token" | "auth/invalid_token" | "auth/expired_token" | "wallet/not_found" | "wallet/insufficient_balance" | "wallet/export_blocked" | "wallet/already_exported" | "user/not_found" | "user/suspended" | "compliance/blocked" | "sign/invalid_transaction" | "sign/wallet_archived" | "sign/internal_failure" | "withdrawal/insufficient_balance" | "withdrawal/limit_exceeded" | "ratelimit/exceeded" | "idempotency/key_in_use" | "validation/bad_request" | "server/internal";
|
|
162
|
+
type IAMGameErrorCode = "auth/invalid_signature" | "auth/challenge_expired" | "auth/challenge_already_redeemed" | "auth/invalid_init_data" | "auth/init_data_stale" | "auth/init_data_replayed" | "auth/missing_token" | "auth/invalid_token" | "auth/expired_token" | "auth/method_not_allowed" | "auth/invalid_otp" | "auth/otp_not_found" | "auth/otp_locked" | "wallet/not_found" | "wallet/insufficient_balance" | "wallet/export_blocked" | "wallet/already_exported" | "user/not_found" | "user/suspended" | "compliance/blocked" | "sign/invalid_transaction" | "sign/wallet_archived" | "sign/internal_failure" | "withdrawal/insufficient_balance" | "withdrawal/limit_exceeded" | "ledger/insufficient_balance" | "ledger/session_not_found" | "ledger/session_closed" | "ledger/bet_not_found" | "ledger/bet_rolled_back" | "ledger/currency_mismatch" | "ratelimit/exceeded" | "idempotency/key_in_use" | "validation/bad_request" | "server/internal";
|
|
150
163
|
interface IIAMGameErrorEnvelope {
|
|
151
164
|
code: IAMGameErrorCode;
|
|
152
165
|
message: string;
|
|
@@ -184,6 +197,13 @@ declare class IAMGameClient {
|
|
|
184
197
|
requestSiwsChallenge(publicKey: string): Promise<ISiwsChallenge>;
|
|
185
198
|
verifySiws(req: ISiwsVerifyRequest): Promise<ISession>;
|
|
186
199
|
verifyTelegram(req: ITelegramVerifyRequest): Promise<ISession>;
|
|
200
|
+
/** Email + OTP: send a one-time code to the address. */
|
|
201
|
+
initiateEmail(email: string): Promise<{
|
|
202
|
+
sent: boolean;
|
|
203
|
+
expiresAt: string;
|
|
204
|
+
}>;
|
|
205
|
+
/** Email + OTP: exchange the code for a session. */
|
|
206
|
+
verifyEmail(email: string, code: string): Promise<ISession>;
|
|
187
207
|
refreshSession(): Promise<ISession | null>;
|
|
188
208
|
logout(): Promise<void>;
|
|
189
209
|
getSession(): ISession | null;
|
|
@@ -241,6 +261,12 @@ interface IUseIAMGameAuth {
|
|
|
241
261
|
connectExternal: (adapter: IExternalWalletAdapter) => Promise<void>;
|
|
242
262
|
/** Run the Telegram TMA flow. Reads `window.Telegram.WebApp.initData` and posts to IAMGame. */
|
|
243
263
|
connectTelegram: () => Promise<void>;
|
|
264
|
+
/** Email + OTP step 1: email a one-time code to the address. */
|
|
265
|
+
requestEmailOtp: (email: string) => Promise<{
|
|
266
|
+
expiresAt: string;
|
|
267
|
+
}>;
|
|
268
|
+
/** Email + OTP step 2: exchange the code for a session. */
|
|
269
|
+
connectEmail: (email: string, code: string) => Promise<void>;
|
|
244
270
|
logout: () => Promise<void>;
|
|
245
271
|
}
|
|
246
272
|
declare function useIAMGameAuth(): IUseIAMGameAuth;
|
|
@@ -297,6 +323,12 @@ interface CommonProps {
|
|
|
297
323
|
* no clicks. Defaults to true.
|
|
298
324
|
*/
|
|
299
325
|
autoTelegram?: boolean;
|
|
326
|
+
/**
|
|
327
|
+
* Show the email + one-time-code login option (web / non-TMA). Defaults to true.
|
|
328
|
+
* The app must also enable `email` in its allowed auth methods, or the send-code
|
|
329
|
+
* call is rejected.
|
|
330
|
+
*/
|
|
331
|
+
showEmail?: boolean;
|
|
300
332
|
}
|
|
301
333
|
type IAMGameLoginProps = CommonProps;
|
|
302
334
|
interface IAMGameLoginModalProps extends CommonProps {
|
package/dist/index.d.ts
CHANGED
|
@@ -1,12 +1,23 @@
|
|
|
1
1
|
import * as react_jsx_runtime from 'react/jsx-runtime';
|
|
2
2
|
import React$1 from 'react';
|
|
3
3
|
|
|
4
|
-
type AuthMethod = "siws" | "telegram";
|
|
4
|
+
type AuthMethod = "siws" | "telegram" | "email";
|
|
5
5
|
interface IUserIdentity {
|
|
6
6
|
id: string;
|
|
7
7
|
type: AuthMethod;
|
|
8
|
-
/** For SIWS: base58 Solana pubkey. For Telegram: numeric user id as string. */
|
|
8
|
+
/** For SIWS: base58 Solana pubkey. For Telegram: numeric user id as string. For email: the address. */
|
|
9
9
|
externalId: string;
|
|
10
|
+
/** Provider-specific profile captured at login (e.g. Telegram { username, firstName }).
|
|
11
|
+
* Display/attribution only — null when none was captured. */
|
|
12
|
+
profile?: Record<string, unknown> | null;
|
|
13
|
+
}
|
|
14
|
+
interface IUserWallet {
|
|
15
|
+
/** base58 Solana pubkey */
|
|
16
|
+
address: string;
|
|
17
|
+
/** test (devnet) or live (mainnet) — wallets are isolated per environment. */
|
|
18
|
+
environment: "test" | "live";
|
|
19
|
+
custody: "self" | "operator";
|
|
20
|
+
status: "active" | "exported" | "archived";
|
|
10
21
|
}
|
|
11
22
|
interface IUser {
|
|
12
23
|
id: string;
|
|
@@ -14,6 +25,8 @@ interface IUser {
|
|
|
14
25
|
studioUserId?: string;
|
|
15
26
|
primaryIdentity: IUserIdentity;
|
|
16
27
|
identities: IUserIdentity[];
|
|
28
|
+
/** Managed wallets owned by this user. Lets a relying server confirm a claimed address. */
|
|
29
|
+
wallets: IUserWallet[];
|
|
17
30
|
createdAt: string;
|
|
18
31
|
}
|
|
19
32
|
/** SIWS challenge issued by the signer; the client signs it with its external wallet. */
|
|
@@ -146,7 +159,7 @@ interface IWebhookSignatureHeader {
|
|
|
146
159
|
signature: string;
|
|
147
160
|
}
|
|
148
161
|
|
|
149
|
-
type IAMGameErrorCode = "auth/invalid_signature" | "auth/challenge_expired" | "auth/challenge_already_redeemed" | "auth/invalid_init_data" | "auth/init_data_stale" | "auth/init_data_replayed" | "auth/missing_token" | "auth/invalid_token" | "auth/expired_token" | "wallet/not_found" | "wallet/insufficient_balance" | "wallet/export_blocked" | "wallet/already_exported" | "user/not_found" | "user/suspended" | "compliance/blocked" | "sign/invalid_transaction" | "sign/wallet_archived" | "sign/internal_failure" | "withdrawal/insufficient_balance" | "withdrawal/limit_exceeded" | "ratelimit/exceeded" | "idempotency/key_in_use" | "validation/bad_request" | "server/internal";
|
|
162
|
+
type IAMGameErrorCode = "auth/invalid_signature" | "auth/challenge_expired" | "auth/challenge_already_redeemed" | "auth/invalid_init_data" | "auth/init_data_stale" | "auth/init_data_replayed" | "auth/missing_token" | "auth/invalid_token" | "auth/expired_token" | "auth/method_not_allowed" | "auth/invalid_otp" | "auth/otp_not_found" | "auth/otp_locked" | "wallet/not_found" | "wallet/insufficient_balance" | "wallet/export_blocked" | "wallet/already_exported" | "user/not_found" | "user/suspended" | "compliance/blocked" | "sign/invalid_transaction" | "sign/wallet_archived" | "sign/internal_failure" | "withdrawal/insufficient_balance" | "withdrawal/limit_exceeded" | "ledger/insufficient_balance" | "ledger/session_not_found" | "ledger/session_closed" | "ledger/bet_not_found" | "ledger/bet_rolled_back" | "ledger/currency_mismatch" | "ratelimit/exceeded" | "idempotency/key_in_use" | "validation/bad_request" | "server/internal";
|
|
150
163
|
interface IIAMGameErrorEnvelope {
|
|
151
164
|
code: IAMGameErrorCode;
|
|
152
165
|
message: string;
|
|
@@ -184,6 +197,13 @@ declare class IAMGameClient {
|
|
|
184
197
|
requestSiwsChallenge(publicKey: string): Promise<ISiwsChallenge>;
|
|
185
198
|
verifySiws(req: ISiwsVerifyRequest): Promise<ISession>;
|
|
186
199
|
verifyTelegram(req: ITelegramVerifyRequest): Promise<ISession>;
|
|
200
|
+
/** Email + OTP: send a one-time code to the address. */
|
|
201
|
+
initiateEmail(email: string): Promise<{
|
|
202
|
+
sent: boolean;
|
|
203
|
+
expiresAt: string;
|
|
204
|
+
}>;
|
|
205
|
+
/** Email + OTP: exchange the code for a session. */
|
|
206
|
+
verifyEmail(email: string, code: string): Promise<ISession>;
|
|
187
207
|
refreshSession(): Promise<ISession | null>;
|
|
188
208
|
logout(): Promise<void>;
|
|
189
209
|
getSession(): ISession | null;
|
|
@@ -241,6 +261,12 @@ interface IUseIAMGameAuth {
|
|
|
241
261
|
connectExternal: (adapter: IExternalWalletAdapter) => Promise<void>;
|
|
242
262
|
/** Run the Telegram TMA flow. Reads `window.Telegram.WebApp.initData` and posts to IAMGame. */
|
|
243
263
|
connectTelegram: () => Promise<void>;
|
|
264
|
+
/** Email + OTP step 1: email a one-time code to the address. */
|
|
265
|
+
requestEmailOtp: (email: string) => Promise<{
|
|
266
|
+
expiresAt: string;
|
|
267
|
+
}>;
|
|
268
|
+
/** Email + OTP step 2: exchange the code for a session. */
|
|
269
|
+
connectEmail: (email: string, code: string) => Promise<void>;
|
|
244
270
|
logout: () => Promise<void>;
|
|
245
271
|
}
|
|
246
272
|
declare function useIAMGameAuth(): IUseIAMGameAuth;
|
|
@@ -297,6 +323,12 @@ interface CommonProps {
|
|
|
297
323
|
* no clicks. Defaults to true.
|
|
298
324
|
*/
|
|
299
325
|
autoTelegram?: boolean;
|
|
326
|
+
/**
|
|
327
|
+
* Show the email + one-time-code login option (web / non-TMA). Defaults to true.
|
|
328
|
+
* The app must also enable `email` in its allowed auth methods, or the send-code
|
|
329
|
+
* call is rejected.
|
|
330
|
+
*/
|
|
331
|
+
showEmail?: boolean;
|
|
300
332
|
}
|
|
301
333
|
type IAMGameLoginProps = CommonProps;
|
|
302
334
|
interface IAMGameLoginModalProps extends CommonProps {
|
package/dist/index.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { createContext, useMemo, useState, useEffect,
|
|
1
|
+
import { createContext, useMemo, useState, useEffect, useRef, useCallback, useContext } from 'react';
|
|
2
2
|
import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
|
|
3
3
|
import { createPortal } from 'react-dom';
|
|
4
4
|
|
|
@@ -78,6 +78,23 @@ var SolvenClient = class {
|
|
|
78
78
|
this.opts.storage.set(session);
|
|
79
79
|
return session;
|
|
80
80
|
}
|
|
81
|
+
/** Email + OTP: send a one-time code to the address. */
|
|
82
|
+
async initiateEmail(email) {
|
|
83
|
+
return this.publicCall(
|
|
84
|
+
"POST",
|
|
85
|
+
"/auth/email/initiate",
|
|
86
|
+
{ email }
|
|
87
|
+
);
|
|
88
|
+
}
|
|
89
|
+
/** Email + OTP: exchange the code for a session. */
|
|
90
|
+
async verifyEmail(email, code) {
|
|
91
|
+
const session = await this.publicCall("POST", "/auth/email/verify", {
|
|
92
|
+
email,
|
|
93
|
+
code
|
|
94
|
+
});
|
|
95
|
+
this.opts.storage.set(session);
|
|
96
|
+
return session;
|
|
97
|
+
}
|
|
81
98
|
async refreshSession() {
|
|
82
99
|
const current = this.opts.storage.get();
|
|
83
100
|
if (!current) return null;
|
|
@@ -252,7 +269,7 @@ function useSolvenContext() {
|
|
|
252
269
|
if (!ctx) throw new Error("useSolvenContext must be inside a <SolvenProvider />");
|
|
253
270
|
return ctx;
|
|
254
271
|
}
|
|
255
|
-
function SolvenProvider({ children, ...opts }) {
|
|
272
|
+
function SolvenProvider({ children, autoTelegram = true, ...opts }) {
|
|
256
273
|
const client = useMemo(() => new SolvenClient(opts), [
|
|
257
274
|
opts.publishableKey,
|
|
258
275
|
opts.baseUrl,
|
|
@@ -293,6 +310,32 @@ function SolvenProvider({ children, ...opts }) {
|
|
|
293
310
|
cancelled = true;
|
|
294
311
|
};
|
|
295
312
|
}, [client]);
|
|
313
|
+
const triedTelegram = useRef(false);
|
|
314
|
+
useEffect(() => {
|
|
315
|
+
if (!autoTelegram || status !== "anonymous" || triedTelegram.current) return;
|
|
316
|
+
triedTelegram.current = true;
|
|
317
|
+
let cancelled = false;
|
|
318
|
+
(async () => {
|
|
319
|
+
let initData = getTelegramInitData();
|
|
320
|
+
for (let i = 0; i < 4 && !initData; i++) {
|
|
321
|
+
await new Promise((r) => setTimeout(r, 400));
|
|
322
|
+
if (cancelled) return;
|
|
323
|
+
initData = getTelegramInitData();
|
|
324
|
+
}
|
|
325
|
+
if (!initData) return;
|
|
326
|
+
try {
|
|
327
|
+
notifyTelegramReady();
|
|
328
|
+
const next = await client.verifyTelegram({ initData });
|
|
329
|
+
if (cancelled) return;
|
|
330
|
+
setSession(next);
|
|
331
|
+
setStatus("authenticated");
|
|
332
|
+
} catch {
|
|
333
|
+
}
|
|
334
|
+
})();
|
|
335
|
+
return () => {
|
|
336
|
+
cancelled = true;
|
|
337
|
+
};
|
|
338
|
+
}, [autoTelegram, status, client]);
|
|
296
339
|
const value = {
|
|
297
340
|
client,
|
|
298
341
|
session,
|
|
@@ -325,6 +368,20 @@ function useSolvenAuth() {
|
|
|
325
368
|
const session2 = await client.verifyTelegram({ initData });
|
|
326
369
|
setSession(session2);
|
|
327
370
|
}, [client, setSession]);
|
|
371
|
+
const requestEmailOtp = useCallback(
|
|
372
|
+
async (email) => {
|
|
373
|
+
const { expiresAt } = await client.initiateEmail(email);
|
|
374
|
+
return { expiresAt };
|
|
375
|
+
},
|
|
376
|
+
[client]
|
|
377
|
+
);
|
|
378
|
+
const connectEmail = useCallback(
|
|
379
|
+
async (email, code) => {
|
|
380
|
+
const session2 = await client.verifyEmail(email, code);
|
|
381
|
+
setSession(session2);
|
|
382
|
+
},
|
|
383
|
+
[client, setSession]
|
|
384
|
+
);
|
|
328
385
|
const logout = useCallback(async () => {
|
|
329
386
|
await client.logout();
|
|
330
387
|
setSession(null);
|
|
@@ -334,6 +391,8 @@ function useSolvenAuth() {
|
|
|
334
391
|
status,
|
|
335
392
|
connectExternal,
|
|
336
393
|
connectTelegram,
|
|
394
|
+
requestEmailOtp,
|
|
395
|
+
connectEmail,
|
|
337
396
|
logout
|
|
338
397
|
};
|
|
339
398
|
}
|
|
@@ -525,9 +584,13 @@ var defaultTheme = {
|
|
|
525
584
|
fontFamily: 'system-ui, -apple-system, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif'
|
|
526
585
|
};
|
|
527
586
|
function SolvenLoginContent(props) {
|
|
528
|
-
const { connectExternal, connectTelegram, status, user } = useSolvenAuth();
|
|
587
|
+
const { connectExternal, connectTelegram, requestEmailOtp, connectEmail, status, user } = useSolvenAuth();
|
|
529
588
|
const [busy, setBusy] = useState(null);
|
|
530
589
|
const [error, setError] = useState(null);
|
|
590
|
+
const [email, setEmail] = useState("");
|
|
591
|
+
const [otp, setOtp] = useState("");
|
|
592
|
+
const [otpSent, setOtpSent] = useState(false);
|
|
593
|
+
const showEmail = props.showEmail ?? true;
|
|
531
594
|
const theme = useMemo(
|
|
532
595
|
() => ({ ...defaultTheme, ...props.theme ?? {} }),
|
|
533
596
|
[props.theme]
|
|
@@ -594,6 +657,31 @@ function SolvenLoginContent(props) {
|
|
|
594
657
|
setBusy(null);
|
|
595
658
|
}
|
|
596
659
|
};
|
|
660
|
+
const handleSendOtp = async () => {
|
|
661
|
+
setBusy("email");
|
|
662
|
+
setError(null);
|
|
663
|
+
try {
|
|
664
|
+
await requestEmailOtp(email.trim());
|
|
665
|
+
setOtpSent(true);
|
|
666
|
+
} catch (e) {
|
|
667
|
+
setError(e instanceof Error ? e.message : "Couldn't send the code");
|
|
668
|
+
} finally {
|
|
669
|
+
setBusy(null);
|
|
670
|
+
}
|
|
671
|
+
};
|
|
672
|
+
const handleVerifyOtp = async () => {
|
|
673
|
+
setBusy("email");
|
|
674
|
+
setError(null);
|
|
675
|
+
try {
|
|
676
|
+
await connectEmail(email.trim(), otp.trim());
|
|
677
|
+
props.onSignIn?.();
|
|
678
|
+
props.onCloseAfterSignIn?.();
|
|
679
|
+
} catch (e) {
|
|
680
|
+
setError(e instanceof Error ? e.message : "Couldn't verify the code");
|
|
681
|
+
} finally {
|
|
682
|
+
setBusy(null);
|
|
683
|
+
}
|
|
684
|
+
};
|
|
597
685
|
const buttonBase = {
|
|
598
686
|
width: "100%",
|
|
599
687
|
padding: "0.75rem 0.875rem",
|
|
@@ -679,6 +767,135 @@ function SolvenLoginContent(props) {
|
|
|
679
767
|
w.id
|
|
680
768
|
);
|
|
681
769
|
}) }),
|
|
770
|
+
!inTma && showEmail && /* @__PURE__ */ jsxs("div", { style: { marginTop: "1rem" }, children: [
|
|
771
|
+
/* @__PURE__ */ jsxs(
|
|
772
|
+
"div",
|
|
773
|
+
{
|
|
774
|
+
style: {
|
|
775
|
+
display: "flex",
|
|
776
|
+
alignItems: "center",
|
|
777
|
+
gap: "0.625rem",
|
|
778
|
+
margin: "0.25rem 0 0.875rem",
|
|
779
|
+
color: theme.muted,
|
|
780
|
+
fontSize: "0.8125rem"
|
|
781
|
+
},
|
|
782
|
+
children: [
|
|
783
|
+
/* @__PURE__ */ jsx("span", { style: { flex: 1, height: 1, background: theme.border } }),
|
|
784
|
+
"or",
|
|
785
|
+
/* @__PURE__ */ jsx("span", { style: { flex: 1, height: 1, background: theme.border } })
|
|
786
|
+
]
|
|
787
|
+
}
|
|
788
|
+
),
|
|
789
|
+
!otpSent ? /* @__PURE__ */ jsxs("div", { style: { display: "flex", flexDirection: "column", gap: "0.5rem" }, children: [
|
|
790
|
+
/* @__PURE__ */ jsx(
|
|
791
|
+
"input",
|
|
792
|
+
{
|
|
793
|
+
type: "email",
|
|
794
|
+
inputMode: "email",
|
|
795
|
+
autoComplete: "email",
|
|
796
|
+
placeholder: "you@email.com",
|
|
797
|
+
value: email,
|
|
798
|
+
onChange: (e) => setEmail(e.target.value),
|
|
799
|
+
style: {
|
|
800
|
+
width: "100%",
|
|
801
|
+
padding: "0.75rem 0.875rem",
|
|
802
|
+
borderRadius: theme.radius,
|
|
803
|
+
border: `1px solid ${theme.border}`,
|
|
804
|
+
background: theme.surface,
|
|
805
|
+
color: theme.foreground,
|
|
806
|
+
fontSize: "0.9375rem",
|
|
807
|
+
outline: "none",
|
|
808
|
+
boxSizing: "border-box"
|
|
809
|
+
}
|
|
810
|
+
}
|
|
811
|
+
),
|
|
812
|
+
/* @__PURE__ */ jsx(
|
|
813
|
+
"button",
|
|
814
|
+
{
|
|
815
|
+
type: "button",
|
|
816
|
+
onClick: handleSendOtp,
|
|
817
|
+
disabled: busy !== null || !email.includes("@"),
|
|
818
|
+
style: {
|
|
819
|
+
...buttonBase,
|
|
820
|
+
justifyContent: "center",
|
|
821
|
+
background: theme.primary,
|
|
822
|
+
color: "#ffffff",
|
|
823
|
+
border: "none",
|
|
824
|
+
opacity: busy === "email" || !email.includes("@") ? 0.6 : 1
|
|
825
|
+
},
|
|
826
|
+
children: busy === "email" ? "Sending\u2026" : "Email me a code"
|
|
827
|
+
}
|
|
828
|
+
)
|
|
829
|
+
] }) : /* @__PURE__ */ jsxs("div", { style: { display: "flex", flexDirection: "column", gap: "0.5rem" }, children: [
|
|
830
|
+
/* @__PURE__ */ jsxs("div", { style: { color: theme.muted, fontSize: "0.8125rem" }, children: [
|
|
831
|
+
"Enter the 6-digit code sent to ",
|
|
832
|
+
/* @__PURE__ */ jsx("strong", { children: email.trim() }),
|
|
833
|
+
"."
|
|
834
|
+
] }),
|
|
835
|
+
/* @__PURE__ */ jsx(
|
|
836
|
+
"input",
|
|
837
|
+
{
|
|
838
|
+
type: "text",
|
|
839
|
+
inputMode: "numeric",
|
|
840
|
+
autoComplete: "one-time-code",
|
|
841
|
+
maxLength: 6,
|
|
842
|
+
placeholder: "123456",
|
|
843
|
+
value: otp,
|
|
844
|
+
onChange: (e) => setOtp(e.target.value.replace(/\D/g, "")),
|
|
845
|
+
style: {
|
|
846
|
+
width: "100%",
|
|
847
|
+
padding: "0.75rem 0.875rem",
|
|
848
|
+
borderRadius: theme.radius,
|
|
849
|
+
border: `1px solid ${theme.border}`,
|
|
850
|
+
background: theme.surface,
|
|
851
|
+
color: theme.foreground,
|
|
852
|
+
fontSize: "1.125rem",
|
|
853
|
+
letterSpacing: "0.3em",
|
|
854
|
+
textAlign: "center",
|
|
855
|
+
outline: "none",
|
|
856
|
+
boxSizing: "border-box"
|
|
857
|
+
}
|
|
858
|
+
}
|
|
859
|
+
),
|
|
860
|
+
/* @__PURE__ */ jsx(
|
|
861
|
+
"button",
|
|
862
|
+
{
|
|
863
|
+
type: "button",
|
|
864
|
+
onClick: handleVerifyOtp,
|
|
865
|
+
disabled: busy !== null || otp.length !== 6,
|
|
866
|
+
style: {
|
|
867
|
+
...buttonBase,
|
|
868
|
+
justifyContent: "center",
|
|
869
|
+
background: theme.primary,
|
|
870
|
+
color: "#ffffff",
|
|
871
|
+
border: "none",
|
|
872
|
+
opacity: busy === "email" || otp.length !== 6 ? 0.6 : 1
|
|
873
|
+
},
|
|
874
|
+
children: busy === "email" ? "Verifying\u2026" : "Verify & sign in"
|
|
875
|
+
}
|
|
876
|
+
),
|
|
877
|
+
/* @__PURE__ */ jsx(
|
|
878
|
+
"button",
|
|
879
|
+
{
|
|
880
|
+
type: "button",
|
|
881
|
+
onClick: () => {
|
|
882
|
+
setOtpSent(false);
|
|
883
|
+
setOtp("");
|
|
884
|
+
setError(null);
|
|
885
|
+
},
|
|
886
|
+
style: {
|
|
887
|
+
background: "none",
|
|
888
|
+
border: "none",
|
|
889
|
+
color: theme.muted,
|
|
890
|
+
fontSize: "0.8125rem",
|
|
891
|
+
cursor: "pointer",
|
|
892
|
+
padding: "0.25rem"
|
|
893
|
+
},
|
|
894
|
+
children: "Use a different email"
|
|
895
|
+
}
|
|
896
|
+
)
|
|
897
|
+
] })
|
|
898
|
+
] }),
|
|
682
899
|
error && /* @__PURE__ */ jsx(
|
|
683
900
|
"div",
|
|
684
901
|
{
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@iamgame/wallet-sdk",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.2",
|
|
4
4
|
"description": "IAMGame Wallet browser SDK — Telegram & Solana wallet auth, balances, server-side signing, and key export. Drop-in React provider for game frontends.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"homepage": "https://wallet.iamgame.com",
|