gala-web3-provider-sdk
v1.0.2
Published
Gala Web3 Provider SDK. A lightweight and easy-to-use JavaScript/TypeScript SDK that provides wallet connection utilities for Gala blockchain applications.
Maintainers
Readme
Gala Web3 Provider SDK Documentation
Demo URL
Release Notes
1.0.0
- Initial release
1.0.1
- Updated README
- Fixed bugs
1.0.2
- Updated README
- Fixed bugs
Prerequisites
- React 19.1.0 or higher
- Node.js 18.17.1 or higher
- npm 9.6.7 or higher
- yarn 4.5.0 or higher
- pnpm 9.14.2 or higher
Overview
The Gala Web3 Provider SDK is a React hook-based utility designed to detect, manage, and connect to EIP-1193-compliant Ethereum wallet providers, with a special focus on the gala provider.
It provides:
- Automatic wallet detection
- Connection management (connect, disconnect)
- Account and chain updates
- Provider events handling
This SDK is lightweight, customizable, and perfect for decentralized applications (dApps) that need a reliable wallet connection.
Installation
# NPM
npm install gala-web3-provider-sdk
# Yarn
yarn add gala-web3-provider-sdk
# PNPM
pnpm add gala-web3-provider-sdkAPI Reference
useGalaProvider(config?: Web3ProviderConfig)
The main hook to interact with wallet providers.
Parameters
config(optional) - Configuration object:preferred:WalletProviderName[]- List of preferred wallet names (default:["gala"])fallbackToAny:boolean- Fallback to any provider if preferred not found (default:true)checkInterval:number- Time interval in ms to re-check providers (default:1000)onAccountsChanged:(accounts: string[]) => void- Callback when user changes accountsonChainChanged:(chainId: string) => void- Callback when chain changesonDisconnect:(error: any) => void- Callback when provider disconnects
Returns
| Property | Type | Description |
| :-------------------------------------------- | :------------------------------------ | :--------------------------------------- |
| providers | DetectedWalletProvider[] | List of all detected providers |
| currentProvider | DetectedWalletProvider \| null | Currently connected provider |
| accounts | string[] | Connected wallet accounts |
| chainId | string \| null | Current network chain ID |
| error | Error \| null | Last encountered error |
| isConnecting | boolean | Indicates if a connection is in progress |
| isDetecting | boolean | Indicates if detection is happening |
| connect(name: WalletProviderName) | Promise<string[]> | Connects to a wallet provider |
| disconnect() | void | Disconnects from the provider |
| getPreferredProvider() | DetectedWalletProvider \| undefined | Get preferred available provider |
| getProviderByName(name: WalletProviderName) | EthereumProvider \| undefined | Get provider by name |
| refreshProviders() | DetectedWalletProvider[] | Force re-detect providers |
Usage Example
1. Basic React Integration
import { useState, useEffect } from 'react';
import { useGalaProvider, WalletProviderName } from 'gala-web3-provider-sdk';
function App() {
const {
providers,
currentProvider,
accounts,
chainId,
error,
isConnecting,
connect,
disconnect,
signMessage,
signTypedData,
sendTransaction,
} = useGalaProvider({
preferred: ['gala'],
onAccountsChanged: (accounts) => {
if (accounts.length === 0) setConnected(false);
},
onChainChanged: (chainId) => {
setChainInfo(chainId);
},
onDisconnect: (error) => {
setConnected(false);
},
});
const [selectedProvider, setSelectedProvider] =
useState<WalletProviderName>('gala');
const [connected, setConnected] = useState(false);
const [chainInfo, setChainInfo] = useState<string | null>(null);
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const [actionResults, setActionResults] = useState<Record<string, any>>({});
const [isActionLoading, setIsActionLoading] = useState<
Record<string, boolean>
>({});
const [messageToSign, setMessageToSign] = useState('Hello Antier!');
const [txParams, setTxParams] = useState({
to: '0x0000000000000000000000000000000000000000',
value: '0x0',
data: '0x',
});
// Auto-select first available provider if current selection isn't available
useEffect(() => {
const availableProviderNames = providers.map((p) => p.name);
if (
availableProviderNames.length > 0 &&
!availableProviderNames.includes(selectedProvider)
) {
setSelectedProvider(availableProviderNames[0]);
}
}, [providers, selectedProvider]);
const handleConnect = async () => {
try {
await connect(selectedProvider);
setConnected(true);
} catch (err) {
console.error(err);
}
};
const handleDisconnect = () => {
disconnect();
setConnected(false);
setActionResults({});
};
const executeWalletAction = async (
actionName: string,
actionFn: () => Promise<unknown>
) => {
try {
setIsActionLoading((prev) => ({ ...prev, [actionName]: true }));
const result = await actionFn();
setActionResults((prev) => ({ ...prev, [actionName]: result }));
console.log(`${actionName} result:`, result);
} catch (err) {
console.error(`${actionName} error:`, err);
setActionResults((prev) => ({
...prev,
[actionName]: err instanceof Error ? err.message : String(err),
}));
} finally {
setIsActionLoading((prev) => ({ ...prev, [actionName]: false }));
}
};
const testSignMessage = async () => {
if (!signMessage) return;
await executeWalletAction('signMessage', () => signMessage(messageToSign));
};
const testSignTypedData = async () => {
if (!signTypedData) return;
const typedData = {
types: {
EIP712Domain: [
{ name: 'name', type: 'string' },
{ name: 'version', type: 'string' },
{ name: 'chainId', type: 'uint256' },
{ name: 'verifyingContract', type: 'address' },
],
Person: [
{ name: 'name', type: 'string' },
{ name: 'wallet', type: 'address' },
],
Mail: [
{ name: 'from', type: 'Person' },
{ name: 'to', type: 'Person' },
{ name: 'contents', type: 'string' },
],
},
primaryType: 'Mail',
domain: {
name: 'Ether Mail',
version: '1',
chainId: chainId ? parseInt(chainId, 16) : 1,
verifyingContract: '0xCcCCccccCCCCcCCCCCCcCcCccCcCCCcCcccccccC',
},
message: {
from: {
name: 'Cow',
wallet: accounts[0],
},
to: {
name: 'Bob',
wallet: '0xbBbBBBBbbBBBbbbBbbBbbbbBBbBbbbbBbBbbBBbB',
},
contents: 'Hello, Bob!',
},
};
await executeWalletAction('signTypedData', () => signTypedData(typedData));
};
const testSendTransaction = async () => {
if (!sendTransaction) return;
await executeWalletAction('sendTransaction', () =>
sendTransaction({
to: '0x4B0897b0513FdBeEc7C469D9aF4fA6C0752aBea7',
from: accounts[0],
gas: '0x76c0',
value: '0x8ac7230489e80000',
data: '0x',
maxFeePerGas: '0x4a817c800',
maxPriorityFeePerGas: '0x4a817c800',
chainId: '0x1',
})
);
};
return (
<div className='app'>
<h1>Gala Wallet Provider Test Suite</h1>
<div className='provider-section'>
<h2>Wallet Connection</h2>
<div className='provider-selection'>
<select
value={selectedProvider}
onChange={(e) =>
setSelectedProvider(e.target.value as WalletProviderName)
}
disabled={connected}
>
{providers.length > 0 ? (
providers.map((provider) => (
<option key={provider.name} value={provider.name}>
{provider.name}
</option>
))
) : (
<option value=''>No providers detected</option>
)}
</select>
{!connected ? (
<button
onClick={handleConnect}
disabled={providers.length === 0 || isConnecting}
>
{isConnecting ? 'Connecting...' : 'Connect Wallet'}
</button>
) : (
<button onClick={handleDisconnect}>Disconnect</button>
)}
</div>
{providers.length === 0 && (
<div className='warning'>
No wallet providers detected. Please install the Gala wallet.
</div>
)}
{error && <div className='error'>{error.message}</div>}
</div>
{connected && currentProvider && (
<div className='wallet-info'>
<h2>Wallet Information</h2>
<div>
<strong>Connected Provider:</strong> {currentProvider.name}
</div>
<div>
<strong>Chain ID:</strong> {chainId}{' '}
{chainInfo && `(Hex: ${chainInfo})`}
</div>
<div>
<strong>Accounts:</strong>
<ul>
{accounts.map((account) => (
<li key={account}>{account}</li>
))}
</ul>
</div>
</div>
)}
{connected && (
<div className='wallet-actions'>
<h2>Wallet Actions</h2>
<div className='action-group'>
<h3>Message Signing</h3>
<div>
<label>
Message to sign:
<input
type='text'
value={messageToSign}
onChange={(e) => setMessageToSign(e.target.value)}
/>
</label>
<button
onClick={testSignMessage}
disabled={isActionLoading.signMessage}
>
{isActionLoading.signMessage ? 'Signing...' : 'Sign Message'}
</button>
{actionResults.signMessage && (
<div className='result'>
<strong>Signature:</strong> {actionResults.signMessage}
</div>
)}
</div>
</div>
<div className='action-group'>
<h3>Typed Data Signing</h3>
<button
onClick={testSignTypedData}
disabled={isActionLoading.signTypedData}
>
{isActionLoading.signTypedData ? 'Signing...' : 'Sign Typed Data'}
</button>
{actionResults.signTypedData && (
<div className='result'>
<strong>Signature:</strong> {actionResults.signTypedData}
</div>
)}
</div>
<div className='action-group'>
<h3>Transaction Actions</h3>
<div>
<label>
To:
<input
type='text'
value={txParams.to}
onChange={(e) =>
setTxParams({ ...txParams, to: e.target.value })
}
/>
</label>
</div>
<div>
<label>
Value (wei):
<input
type='text'
value={txParams.value}
onChange={(e) =>
setTxParams({ ...txParams, value: e.target.value })
}
/>
</label>
</div>
<div>
<label>
Data:
<input
type='text'
value={txParams.data}
onChange={(e) =>
setTxParams({ ...txParams, data: e.target.value })
}
/>
</label>
</div>
<div className='action-buttons'>
<button
onClick={testSendTransaction}
disabled={isActionLoading.sendTransaction}
>
{isActionLoading.sendTransaction
? 'Sending...'
: 'Send Transaction'}
</button>
</div>
{actionResults.signTransaction && (
<div className='result'>
<strong>Signed Tx:</strong> {actionResults.signTransaction}
</div>
)}
{actionResults.sendTransaction && (
<div className='result'>
<strong>Tx Hash:</strong> {actionResults.sendTransaction}
</div>
)}
{actionResults.sendRawTransaction && (
<div className='result'>
<strong>Raw Tx Hash:</strong> {actionResults.sendRawTransaction}
</div>
)}
</div>
</div>
)}
<div className='providers-list'>
<h2>Detected Providers</h2>
<ul>
{providers.map((provider) => (
<li key={provider.name}>
{provider.name}{' '}
{currentProvider?.name === provider.name && '(Connected)'}
</li>
))}
</ul>
</div>
</div>
);
}
export default App;Concepts Explained
1. Provider Detection
- The hook periodically checks if a provider like
galais injected intowindow.gala. - When a provider is detected or removed, the
onProvidersChangedcallback fires.
2. Connection Lifecycle
connect()triggerseth_requestAccountsandeth_chainIdrequests.- Wallet events (
accountsChanged,chainChanged,disconnect) are handled automatically. disconnect()clears listeners and resets connection state.
3. Fallback Behavior
- If no preferred provider is found and
fallbackToAny: true, the hook will fallback to the first available provider.
4. Events Handling
- Accounts Change: Updates
accounts. - Chain Change: Updates
chainId. - Disconnect: Resets all internal states.
Types
WalletProviderName
type WalletProviderName = 'gala';EIP1559Transaction
interface EIP1559Transaction {
/**
* The recipient's address (optional for contract creation)
* @pattern ^0x[0-9a-fA-F]{40}$
* @example "0x742d35Cc6634C0532925a3b844Bc454e4438f44e"
*/
to?: string;
/**
* The sender's address
* @pattern ^0x[0-9a-fA-F]{40}$
* @example "0x742d35Cc6634C0532925a3b844Bc454e4438f44e"
*/
from: string;
/**
* The maximum amount of gas the transaction is allowed to use
* @pattern ^0x([1-9a-f]+[0-9a-f]*|0)$
* @example "0x5208" (21000 gas)
*/
gas?: string;
/**
* The amount to transfer in wei
* @pattern ^0x([1-9a-f]+[0-9a-f]*|0)$
* @example "0x16345785d8a0000" (100 ether in wei)
*/
value?: string;
/**
* The data payload (used for contract calls/creation)
* @pattern ^0x[0-9a-f]*$
* @example "0xa9059cbb000000000000000000000000..."
*/
data?: string;
/**
* Maximum fee per gas (includes base + priority fee) in wei
* @pattern ^0x([1-9a-f]+[0-9a-f]*|0)$
* @example "0x2540be400" (100 gwei)
*/
maxFeePerGas: string;
/**
* Maximum priority fee per gas (tip to miner) in wei
* @pattern ^0x([1-9a-f]+[0-9a-f]*|0)$
* @example "0x3b9aca00" (1 gwei)
*/
maxPriorityFeePerGas: string;
/**
* Chain ID (EIP-155)
* @example "0x1" for Ethereum Mainnet
*/
chainId?: string;
}AvailableEIP1193Methods
type AvailableEIP1193Methods =
| 'eth_requestAccounts'
| 'eth_chainId'
| 'eth_signTypedData_v4'
| 'personal_sign'
| 'eth_sendTransaction'
| 'eth_accounts';EthereumProvider
interface EthereumProvider {
isGala?: boolean;
request: (args: {
method: AvailableEIP1193Methods;
params?: any[];
}) => Promise<any>;
on?: (event: string, handler: (...args: any[]) => void) => void;
removeListener?: (event: string, handler: (...args: any[]) => void) => void;
[key: string]: any;
}DetectedWalletProvider
interface DetectedWalletProvider {
name: WalletProviderName;
provider: EthereumProvider;
}Web3ProviderConfig
interface Web3ProviderConfig {
/**
* List of wallet provider names to prioritize when selecting a provider.
* Defaults to ["metamask", "trustwallet", "gala"].
*/
preferred?: WalletProviderName[];
/**
* Whether to fallback to any available provider if no preferred ones are found.
* Defaults to true.
*/
fallbackToAny?: boolean;
/**
* Callback when accounts are changed.
*/
onAccountsChanged?: (accounts: string[]) => void;
/**
* Callback when the chain changes.
*/
onChainChanged?: (chainId: string) => void;
/**
* Callback when the provider disconnects.
*/
onDisconnect?: (error: any) => void;
/**
* Callback when the list of available providers changes.
*/
onProvidersChanged?: (providers: DetectedWalletProvider[]) => void;
/**
* Time interval (in ms) to check for new providers. Defaults to 1000ms.
* Set to 0 to disable periodic checking.
*/
checkInterval?: number;
}Final Notes
- This SDK focuses primarily on the Gala wallet integration but can be extended to other wallets easily.
- The design emphasizes simplicity, flexibility, and event safety.
- Feel free to modify the
WalletProviderNametype anddetectProviderslogic to support more wallets!
License
MIT License
Author
Antier PVT LTD.
Happy building decentralized apps with Gala Web3 Provider SDK! ✨
