Getting Started
We've streamlined the authentication process to support both traditional and Web3 users. We leverage Web3Auth for seamless social logins and RainbowKit for native wallet connections. Behind the scenes, we're using account abstraction to generate smart contract wallets, making the onboarding experience smooth for both crypto-natives and newcomers.
Want to integrate your own wallet connection? No problem. Our platform is built on top of wagmi, so you can plug in any wallet connector that wagmi supports.
The platform leverages modern web technologies and blockchain integration through wagmi/viem, providing a seamless gaming experience while maintaining the security and transparency of blockchain transactions.
'use client';
import { SmartWalletConnectors } from '@winrlabs/web3';
import { defineChain, fallback, HttpTransport } from 'viem';
import { arbitrum, base, mainnet } from 'viem/chains';
import { http, createConfig, Config } from 'wagmi';
import { connectorsForWallets } from '@rainbow-me/rainbowkit';
import { extraRpcs } from './rpcs';
import {
metaMaskWallet,
rabbyWallet,
okxWallet,
walletConnectWallet,
foxWallet,
coinbaseWallet,
trustWallet,
} from '@rainbow-me/rainbowkit/wallets';
import {
ARBITRUM_CHAIN_ID,
BASE_CHAIN_ID,
BSC_CHAIN_ID,
ETH_CHAIN_ID,
WINR_CHAIN_ID,
WINR_CHAIN_ID_MAP,
} from './constants';
export const winrChainParamsMap = {
[WINR_CHAIN_ID_MAP.mainnet]: {
blockExplorerUrls: ['https://explorer.winr.games'],
chainName: 'WINR Chain',
nativeCurrency: {
decimals: 18,
name: 'WINR',
symbol: 'WINR',
},
rpcUrls: ['https://rpc.winr.games'],
},
[WINR_CHAIN_ID_MAP.testnet]: {
blockExplorerUrls: ['https://explorer-winrprotocoltestnet-wmwv59m23g.t.conduit.xyz'],
chainName: 'WINR Chain Testnet',
nativeCurrency: {
decimals: 18,
name: 'WINR',
symbol: 'WINR',
},
rpcUrls: ['https://rpc-winrprotocoltestnet-wmwv59m23g.t.conduit.xyz'],
},
};
export const winrChainParams = winrChainParamsMap[WINR_CHAIN_ID];
export const winrMainChain = defineChain({
id: WINR_CHAIN_ID_MAP.mainnet,
name: 'WINR Chain',
network: 'winr',
nativeCurrency: { name: 'WINR', symbol: 'WINR', decimals: 18 },
rpcUrls: {
default: {
http: ['https://rpc.winr.games'],
},
public: {
http: ['https://rpc.winr.games'],
},
},
blockExplorers: {
etherscan: {
name: 'WINRscan',
url: 'https://explorer.winr.games',
},
default: {
name: 'WINRscan',
url: 'https://explorer.winr.games',
},
},
});
export const winrChainTestnet = defineChain({
id: WINR_CHAIN_ID_MAP.testnet,
name: 'WINR Chain Testnet',
network: 'winr',
testnet: true,
nativeCurrency: { name: 'WINR', symbol: 'WINR', decimals: 18 },
rpcUrls: {
default: {
http: ['https://rpc-winrprotocoltestnet-wmwv59m23g.t.conduit.xyz'],
},
public: {
http: ['https://rpc-winrprotocoltestnet-wmwv59m23g.t.conduit.xyz'],
},
},
blockExplorers: {
etherscan: {
name: 'WINRscan',
url: 'https://explorer-winrprotocoltestnet-wmwv59m23g.t.conduit.xyz',
},
default: {
name: 'WINRscan',
url: 'https://explorer-winrprotocoltestnet-wmwv59m23g.t.conduit.xyz',
},
},
});
export const winrChain =
process.env.NEXT_PUBLIC_NETWORK_MODE === 'testnet' ? winrChainTestnet : winrMainChain;
export const smartWalletConnectors = new SmartWalletConnectors({
chains: [winrChain],
loginProviders: ['google', 'weibo', 'twitter', 'facebook', 'twitch', 'line', 'discord'],
web3AuthOptions: {
clientId: process.env.NEXT_PUBLIC_WEB3AUTH_CLIENT_ID || '',
web3AuthNetwork: (process.env.NEXT_PUBLIC_WEB3AUTH_NETWORK || '') as any,
sessionTime: 86400 * 7,
},
openLoginOptions: {
adapterSettings: {
uxMode: 'popup',
network: (process.env.NEXT_PUBLIC_WEB3AUTH_NETWORK || '') as any,
clientId: process.env.NEXT_PUBLIC_WEB3AUTH_CLIENT_ID || '',
whiteLabel: {
appName: 'JIB',
appUrl: 'https://justbet-aa.vercel.app/',
logoLight: 'https://web3auth.io/images/w3a-L-Favicon-1.svg',
logoDark: 'https://web3auth.io/images/w3a-D-Favicon-1.svg',
defaultLanguage: 'en', // en, de, ja, ko, zh, es, fr, pt, nl
theme: {
primary: '#00B4FF',
},
},
},
},
});
const connectors = connectorsForWallets(
[
{
groupName: 'Recommended',
wallets: [
metaMaskWallet,
rabbyWallet,
foxWallet,
coinbaseWallet,
trustWallet,
okxWallet,
walletConnectWallet,
],
},
],
{
appName: 'JustBet',
projectId: 'df813ab4d5b90931c591c84cc120d5aa',
}
);
export const arbitrumRpcs = extraRpcs[ARBITRUM_CHAIN_ID]?.rpcs.map((rpc) => {
if (typeof rpc === 'string') {
return http(rpc);
} else {
return http(rpc.url);
}
}) as HttpTransport[];
export const bscRpcs = extraRpcs[BSC_CHAIN_ID]?.rpcs.map((rpc) => {
if (typeof rpc === 'string') {
return http(rpc);
} else {
return http(rpc.url);
}
}) as HttpTransport[];
export const ethRpcs = extraRpcs[ETH_CHAIN_ID]?.rpcs.map((rpc) => {
if (typeof rpc === 'string') {
return http(rpc);
} else {
return http(rpc.url);
}
}) as HttpTransport[];
export const baseRpcs = extraRpcs[BASE_CHAIN_ID]?.rpcs.map((rpc) => {
if (typeof rpc === 'string') {
return http(rpc);
} else {
return http(rpc.url);
}
}) as HttpTransport[];
// const ethHttp = http(ethRpcs[0].url);
export const config = createConfig({
chains: [winrChain, arbitrum, base, mainnet],
connectors: [
...smartWalletConnectors.connectors.map(({ connector }) => connector),
...connectors,
],
ssr: false,
transports: {
[winrChain.id]: http(),
[arbitrum.id]: fallback(arbitrumRpcs),
[base.id]: fallback(baseRpcs),
[mainnet.id]: http(),
},
}) as Config;
export const optimismRpcs = extraRpcs[10]?.rpcs.map((rpc) => {
if (typeof rpc === 'string') {
return http(rpc);
} else {
return http(rpc.url);
}
}) as HttpTransport[];
export const winrChainRpcs = extraRpcs[WINR_CHAIN_ID]?.rpcs.map((rpc) => {
if (typeof rpc === 'string') {
return http(rpc);
} else {
return http(rpc.url);
}
}) as HttpTransport[];And then you can use it in a login modal like this:
'use client';
import { Connector, useConnect, useConnectors, useSwitchChain } from 'wagmi';
import {
BundlerNetwork,
fetchBundlerClient,
SmartWalletConnectorWagmiType,
useCreateSession,
useSessionStore,
} from '@winrlabs/web3';
import { useEffect } from 'react';
import React from 'react';
import {
Button,
Dialog,
DialogBody,
DialogContent,
DialogHeader,
DialogTitle,
IconGoogle,
IconWallet,
Spinner,
useModalsStore,
useToast,
} from '@winrlabs/ui';
import Image from 'next/image';
import { config, winrChainParams } from '../../../../wagmi';
import { useQueryClient } from '@tanstack/react-query';
import debug from 'debug';
import { WINR_CHAIN_ID } from '../../../../constants';
import { getWalletClient } from '@wagmi/core';
import { useConnectModal } from '@rainbow-me/rainbowkit';
const log = debug('worker:LoginModal');
export const Connecting = () => {
return (
<div className="flex h-64 items-center justify-center gap-2">
<Spinner />
<span className="text-primary">Connecting...</span>
</div>
);
};
export const LoginModal = () => {
const { modal, closeModal } = useModalsStore();
const queryCache = useQueryClient();
useEffect(() => {
if (modal === 'login') {
localStorage.clear();
}
}, [modal]);
const { toast } = useToast();
const connectors = useConnectors();
const { isPending, isSuccess, connectAsync, error, isError } = useConnect();
const { switchChainAsync } = useSwitchChain();
const createSession = useCreateSession();
useEffect(() => {
if (!isError) return;
if (isError) {
if (error.message.includes('Provider not found')) {
toast({
title: 'OOPS!',
description: 'Selected wallet provider is not found.',
variant: 'destructive',
});
} else {
toast({
title: 'OOPS!',
description: error.message,
variant: 'destructive',
});
}
}
}, [isError, error]);
useEffect(() => {
if (!closeModal) return;
if (isSuccess) {
closeModal();
}
}, [closeModal, isSuccess]);
const smartWalletConnectors = connectors.filter(
(connector) =>
connector.type === SmartWalletConnectorWagmiType && connector.id !== 'web3auth-google'
);
const googleConnector = connectors.find((connector) => connector.id === 'web3auth-google');
const session = useSessionStore();
const handleConnect = async (connector: Connector) => {
localStorage?.clear();
const data = await connectAsync({ connector });
localStorage['isConnected'] = true;
localStorage['recentConnectorId'] = connector.id;
try {
await switchChainAsync({
chainId: WINR_CHAIN_ID,
connector,
addEthereumChainParameter: winrChainParams,
});
} catch (e) {
localStorage.clear();
window.location.reload();
return;
}
const bundlerClient = await fetchBundlerClient({
network: BundlerNetwork.WINR,
rpcUrl: process.env.NEXT_PUBLIC_BUNDLER_URL || 'https://hub.winr.games/rpc',
walletAddress: data.accounts[0],
});
const walletClient = await getWalletClient(config, {
account: data.accounts[0],
chainId: WINR_CHAIN_ID,
});
const { part, permit } = await createSession.mutateAsync({
signerAddress: data.accounts[0],
customWalletClient: walletClient,
untilInHours: 24,
customClient: bundlerClient,
});
session.setPart(part);
session.setPermit(permit);
queryCache.invalidateQueries({
queryKey: ['currentUserAddress'],
});
};
const { openConnectModal, connectModalOpen } = useConnectModal();
console.log('connectModalOpen', connectModalOpen);
return (
<Dialog
onOpenChange={(d) => {
log('OnOpenChange', d);
}}
open={modal === 'login'}
>
<DialogContent className="sm:max-w-[420px]">
<DialogHeader>
<DialogTitle className="flex items-center gap-2">
<IconWallet className="h-6 w-6" />
Login or Sign-Up
</DialogTitle>
</DialogHeader>
<DialogBody className="p-4">
{isPending && <Connecting />}
{!isPending && (
<React.Fragment>
<section>
<button
className="flex h-10 w-full items-center justify-center gap-1 rounded-md bg-zinc-100 px-3 py-0 text-[15px] font-semibold leading-4 text-black transition duration-300 ease-out hover:bg-zinc-300 hover:ease-in focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-offset-2 disabled:pointer-events-none disabled:cursor-not-allowed"
onClick={async () => {
await handleConnect(googleConnector as Connector);
}}
>
<IconGoogle />
<span>Continue with Google</span>
</button>
</section>
<section className="my-3 grid grid-cols-2 gap-2">
{smartWalletConnectors.map((connector) => {
return (
<div key={connector.id} className="flex flex-col gap-2">
<Button
variant={'fourth'}
size={'lg'}
className="gap-2 capitalize"
onClick={async () => {
await handleConnect(connector);
}}
type="button"
>
<Image
src={`/images/connectors/${connector.name}.png`}
width={20}
height={20}
alt={`connector_${connector.name}`}
/>
{connector.name === 'twitter' ? 'X (Twitter)' : connector.name}
</Button>
</div>
);
})}
</section>
<section className="mt-3">
<Button
variant={'success'}
size={'lg'}
className="w-full"
onClick={() => {
closeModal();
openConnectModal?.();
}}
>
Connect Wallet
</Button>
</section>
</React.Fragment>
)}
</DialogBody>
</DialogContent>
</Dialog>
);
};