aelf-web-login: Modular React wallet collection and components for aelf applications.
website: https://aelf-web-login.vercel.app/
package | Tests | Coverage |
---|---|---|
@aelf-web-login/utils | ||
@aelf-web-login/base | ||
@aelf-web-login/bridge | ||
@aelf-web-login/react |
yarn add @aelf-web-login/wallet-adapter-night-elf @aelf-web-login/wallet-adapter-portkey-aa @aelf-web-login/wallet-adapter-portkey-discover @aelf-web-login/wallet-adapter-react @aelf-web-login/wallet-adapter-base @aelf-web-login/wallet-adapter-bridge @aelf-web-login/utils @portkey/did-ui-react
Then the package.json
will be like this
"dependencies": {
"@aelf-web-login/wallet-adapter-night-elf": "^0.1.7",
"@aelf-web-login/wallet-adapter-portkey-aa": "^0.1.7",
"@aelf-web-login/wallet-adapter-portkey-discover": "^0.1.7",
"@aelf-web-login/wallet-adapter-react": "^0.1.7",
"@aelf-web-login/wallet-adapter-base": "^0.1.7",
"@aelf-web-login/wallet-adapter-bridge": "^0.1.7",
"@aelf-web-login/utils": "^0.1.7",
"@portkey/did-ui-react": "^2.13.2",
}
- Import
PortkeyDiscoverWallet
,PortkeyAAWallet
andNightElfWallet
and generate instance - Create
didConfig
(internal invoke:ConfigProvider.setGlobalConfig(didConfig)
) - Create
baseConfig
forSignIn
component - Create
wallets
by wallet instance - Combine them into a whole as
config
import { PortkeyDiscoverWallet } from '@aelf-web-login/wallet-adapter-portkey-discover';
import { PortkeyAAWallet } from '@aelf-web-login/wallet-adapter-portkey-aa';
import { NightElfWallet } from '@aelf-web-login/wallet-adapter-night-elf';
import { IConfigProps } from '@aelf-web-login/wallet-adapter-bridge';
import { TChainId, SignInDesignEnum, NetworkEnum } from '@aelf-web-login/wallet-adapter-base';
const APP_NAME = 'explorer.aelf.io';
const WEBSITE_ICON = 'https://explorer.aelf.io/favicon.main.ico';
const CHAIN_ID = 'AELF' as TChainId;
const NETWORK_TYPE = NetworkEnum.TESTNET;
const RPC_SERVER_AELF = 'https://aelf-test-node.aelf.io';
const RPC_SERVER_TDVV = 'https://tdvv-public-node.aelf.io';
const RPC_SERVER_TDVW = 'https://tdvw-test-node.aelf.io';
const GRAPHQL_SERVER =
'https://dapp-aa-portkey-test.portkey.finance/aefinder-v2/api/app/graphql/portkey';
const CONNECT_SERVER = 'https://auth-aa-portkey-test.portkey.finance';
const SERVICE_SERVER = 'https://aa-portkey-test.portkey.finance';
const TELEGRAM_BOT_ID = 'xx';
const didConfig = {
graphQLUrl: GRAPHQL_SERVER,
connectUrl: CONNECT_SERVER,
serviceUrl: SERVICE_SERVER,
requestDefaults: {
baseURL: SERVICE_SERVER,
timeout: 30000,
},
socialLogin: {
Portkey: {
websiteName: APP_NAME,
websiteIcon: WEBSITE_ICON,
},
Telegram: {
botId: TELEGRAM_BOT_ID,
},
},
// customNetworkType: NETWORK_TYPE === 'TESTNET' ? 'offline' : 'online',
// loginConfig: {
// loginMethodsOrder: [ "Email", "Google" , "Apple" , "Scan"]
// }
};
const baseConfig = {
// ConfirmLogoutDialog: CustomizedConfirmLogoutDialog,
// SignInComponent: SignInProxy,
// defaultPin: '111111',
// PortkeyProviderProps: {
// theme: 'light' as any,
// },
// omitTelegramScript: false,
// cancelAutoLoginInTelegram: false,
enableAcceleration: false,
networkType: NETWORK_TYPE,
showVconsole: false,
chainId: CHAIN_ID,
keyboard: true,
noCommonBaseModal: false,
design: SignInDesignEnum.CryptoDesign, // "SocialDesign" | "CryptoDesign" | "Web2Design"
titleForSocialDesign: 'Crypto wallet',
iconSrcForSocialDesign: 'url or base64',
};
const wallets = [
new PortkeyAAWallet({
appName: APP_NAME,
chainId: CHAIN_ID,
autoShowUnlock: true,
noNeedForConfirm: false,
}),
new PortkeyDiscoverWallet({
networkType: NETWORK_TYPE,
chainId: CHAIN_ID,
autoRequestAccount: true, // If set to true, please contact Portkey to add whitelist @Rachel
autoLogoutOnDisconnected: true,
autoLogoutOnNetworkMismatch: true,
autoLogoutOnAccountMismatch: true,
autoLogoutOnChainMismatch: true,
}),
new NightElfWallet({
chainId: CHAIN_ID,
appName: APP_NAME,
connectEagerly: true,
defaultRpcUrl: RPC_SERVER_AELF,
nodes: {
AELF: {
chainId: 'AELF',
rpcUrl: RPC_SERVER_AELF,
},
tDVW: {
chainId: 'tDVW',
rpcUrl: RPC_SERVER_TDVW,
},
tDVV: {
chainId: 'tDVV',
rpcUrl: RPC_SERVER_TDVV,
},
},
}),
];
const config: IConfigProps = {
didConfig,
baseConfig,
wallets,
};
IConfigProps Clarifications
import { GlobalConfigProps } from '@portkey/did-ui-react/dist/_types/src/components/config-provider/types';
import { SignInProps, ISignIn, PortkeyProvider } from '@portkey/did-ui-react';
import { WalletAdapter } from '@aelf-web-login/wallet-adapter-base';
interface IConfirmLogoutDialogProps {
title: string;
subTitle: string[];
okTxt: string;
cancelTxt: string;
visible: boolean;
onOk: () => void;
onCancel: () => void;
width: number;
mobileWidth: number;
}
interface IConfigProps {
didConfig: GlobalConfigProps;
baseConfig: IBaseConfig;
wallets: WalletAdapter[];
}
interface IBaseConfig {
networkType: NetworkEnum;
chainId: TChainId;
keyboard?: boolean;
design?: SignInDesignEnum;
iconSrcForSocialDesign?: string;
titleForSocialDesign?: string;
noCommonBaseModal?: boolean;
showVconsole?: boolean;
SignInComponent?: React.FC<SignInProps & RefAttributes<ISignIn>>;
PortkeyProviderProps?: Partial<Omit<React.ComponentProps<typeof PortkeyProvider>, 'children'>>;
ConfirmLogoutDialog?: React.FC<Partial<IConfirmLogoutDialogProps>>;
}
- Import
WebLoginProvider
,init
anduseConnectWallet
- invoke
init
with upper config as params - pass the return value
bridgeAPI
toWebLoginProvider
- use
useConnectWallet
to consumebridgeAPI
import { WebLoginProvider, init, useConnectWallet } from '@aelf-web-login/wallet-adapter-react';
const App = () => {
const bridgeAPI = init(config); // upper config
return (
<WebLoginProvider bridgeAPI={bridgeAPI}>
<Demo />
</WebLoginProvider>
);
};
const Demo = () => {
const {
connectWallet,
disConnectWallet,
walletInfo,
lock,
isLocking,
isConnected,
loginError,
walletType,
getAccountByChainId,
getWalletSyncIsCompleted,
getSignature,
callSendMethod,
callViewMethod,
} = useConnectWallet();
};
connectWallet: () => Promise<TWalletInfo>;
Connect wallet and return walletInfo
import { Button } from 'aelf-design';
const Demo = () => {
const { connectWallet } = useConnectWallet();
const onConnectBtnClickHandler = async () => {
try {
const rs = await connectWallet();
} catch (e: any) {
console.log(e.message);
}
};
return <Button onClick={onConnectBtnClickHandler}>connect</Button>;
};
disConnectWallet: () => Promise<void>;
Disconnect wallet
import { Button } from 'aelf-design';
const Demo = () => {
const { disConnectWallet } = useConnectWallet();
const onDisConnectBtnClickHandler = () => {
disConnectWallet();
};
return <Button onClick={onDisConnectBtnClickHandler}>disConnect</Button>;
};
lock: () => void
Lock wallet, only portkeyAA wallet take effect
import { Button } from 'aelf-design';
const Demo = () => {
const { lock } = useConnectWallet();
return <Button onClick={lock}>lock</Button>;
};
getAccountByChainId: (chainId: TChainId) => Promise<string>;
Get account address of designative chainId
import { Button } from 'aelf-design';
const Demo = () => {
const { getAccountByChainId } = useConnectWallet();
const getAelfAccountHandler = async() => {
const address = await getAccountByChainId('AELF')
console.log(address)
}
const getTdvwAccountHandler = async() => {
const address = await getAccountByChainId('tDVW')
console.log(address)
}
return (
<Button onClick={getAelfAccountHandler}>account-AELF</Button>
<Button onClick={getTdvwAccountHandler}>account-tDVW</Button>
)
}
getWalletSyncIsCompleted: (chainId: TChainId) => Promise<string | boolean>;
Return account address of designative chainId if sync is competed, otherwise return false
import { Button } from 'aelf-design';
const Demo = () => {
const { getWalletSyncIsCompleted } = useConnectWallet();
const getAelfSyncIsCompletedHandler = async() => {
const address = await getWalletSyncIsCompleted('AELF')
console.log(address)
}
const getTdvwSyncIsCompletedHandler = async() => {
const address = await getWalletSyncIsCompleted('tDVW')
console.log(address)
}
return (
<Button onClick={getAelfSyncIsCompletedHandler}>sync-AELF</Button>
<Button onClick={getTdvwSyncIsCompletedHandler}>sync-tDVW</Button>
)
}
const getSignature: (
params: TSignatureParams,
) => Promise<{ error: number; errorMessage: string; signature: string; from: string } | null>;
Get signature message
import { Button, Input } from 'aelf-design';
type TSignatureParams = {
appName: string;
address: string;
signInfo: string;
hexToBeSign?: string;
};
const Demo = () => {
const { getSignature } = useConnectWallet();
const [signInfo, setSignInfo] = useState('');
const [signedMessage, setSignedMessage] = useState('');
const signHandler = async () => {
const sign = await getSignature({
signInfo,
appName: '',
address: '',
});
setSignedMessage(sign.signature);
};
return (
div>
<div>
<Button onClick={signHandler}>
Sign
</Button>
<Input value={signInfo} onChange={(e) => setSignInfo(e.target.value)} />
<div>{signedMessage}</div>
</div>
</div>
)
}
callSendMethod: <T, R>(props: ICallContractParams<T>) => Promise<R>;
Call contract's send method
import { Button } from 'aelf-design';
interface ICallContractParams<T> {
contractAddress: string;
methodName: string;
args: T;
chainId?: TChainId;
sendOptions?: SendOptions;
}
const Demo = () => {
const { callSendMethod } = useConnectWallet();
const [result, setResult] = useState({});
const onApproveHandler = async () => {
const res = await callSendMethod({
chainId: 'tDVW',
contractAddress: 'JRmBduh4nXWi1aXgdUsj5gJrzeZb2LxmrAbf7W99faZSvoAaE',
methodName: 'Approve',
args: {
symbol: 'ELF',
spender: 'JRmBduh4nXWi1aXgdUsj5gJrzeZb2LxmrAbf7W99faZSvoAaE',
amount: '100000000',
},
});
setResult(res);
};
return (
<div>
<Button onClick={onApproveHandler}>Approve in tDVW</Button>
<div>
<h4>Result</h4>
<pre className="result">{JSON.stringify(result, null, ' ')}</pre>
</div>
</div>
);
};
callViewMethod: <T, R>(props: ICallContractParams<T>) => Promise<R>;
Call contract's view method
import { Button } from 'aelf-design';
interface ICallContractParams<T> {
contractAddress: string;
methodName: string;
args: T;
chainId?: TChainId;
sendOptions?: SendOptions; // only send method use, ignore in view method
}
const Demo = () => {
const { callViewMethod, getAccountByChainId } = useConnectWallet();
const [result, setResult] = useState({});
const onGetBalanceHandler = async () => {
const res = await callViewMethod({
chainId: 'tDVW',
contractAddress: 'ASh2Wt7nSEmYqnGxPPzp4pnVDU4uhj1XW9Se5VeZcX2UDdyjx',
methodName: 'GetBalance',
args: {
symbol: 'ELF',
owner: await getAccountByChainId('tDVW'),
},
});
setResult(res);
};
return (
<div>
<Button onClick={onGetBalanceHandler}>GetBalance in tDVW</Button>
<div>
<h4>Result</h4>
<pre className="result">{JSON.stringify(result, null, ' ')}</pre>
</div>
</div>
);
};
const walletInfo: TWalletInfo;
Wallet information after connecting wallet, can import
TWalletInfo
from@aelf-web-login/wallet-adapter-base
type TWalletInfo =
| {
name?: string;
address: string;
extraInfo?: {
[key: string]: any;
};
}
| undefined;
// walletInfo returned by nightElf
{
name,
address,
extraInfo: {
publicKey,
nightElfInfo: {
name,
appPermission,
defaultAElfBridge: bridge,
aelfBridges: bridges,
nodes,
},
},
}
// walletInfo returned by portkeyAA
import { DIDWalletInfo } from '@portkey/did-ui-react';
{
name,
address,
extraInfo: {
publicKey,
portkeyInfo: {
...DIDWalletInfo
accounts: {
[chainId]: didWalletInfo.caInfo?.caAddress,
},
nickName,
},
},
}
// walletInfo returned by portkeyDiscover
import type { Accounts, IPortkeyProvider } from '@portkey/provider-types';
{
address,
extraInfo: {
accounts: Accounts,
nickName,
provider: IPortkeyProvider,
},
}
const Demo = () => {
const { walletInfo } = useConnectWallet();
console.log(walletInfo)
return null
}
const walletType: WalletTypeEnum;
The currently connected wallet type, can import
WalletTypeEnum
from@aelf-web-login/wallet-adapter-base
enum WalletTypeEnum {
unknown = 'Unknown',
elf = 'NightElf',
aa = 'PortkeyAA',
discover = 'PortkeyDiscover',
}
const Demo = () => {
const { walletType } = useConnectWallet();
console.log(walletType);
return null;
};
const isLocking: boolean;
indicate whether the current state is locked, only portkeyAA wallet take effect, other wallets always return false
import { Button } from 'aelf-design';
const Demo = () => {
const { isLocking } = useConnectWallet();
return <Button>{isLocking ? 'unlock' : 'connect'}</Button>;
};
const isConnected: boolean;
indicate whether the current state is connected
import { Button } from 'aelf-design';
const Demo = () => {
const { isConnected } = useConnectWallet();
return (
<div>
<Button disabled={isConnected}>connect</Button>
<Button disabled={!isConnected}>disConnect</Button>
</div>
);
};
const loginError: TWalletError | null;
indicate are there any errors during the login/logout/unlock process
type TWalletError = {
name: string;
code: number;
message: string;
nativeError?: any;
};
const Demo = () => {
const { loginError } = useConnectWallet();
useEffect(() => {
if (!loginError) {
return;
}
console.log(loginError.message);
}, [loginError]);
return null;
};
- Install dependencies in the project root directory
pnpm install
- cd to demo directory and execute dev command
cd packages/starter
pnpm dev
OR directly filter workspace package pnpm --filter "@aelf-web-login/doc-site" dev
- Upgrade the version numbers of each sub package
- execute release command in the project root directory
pnpm release