Wallet Integration and State Management
Last updated
// src/store/wallet.ts
export interface WalletApiProvider {
/**
* Retrieves a wallet API instance for the specified provider
*
* @param name - Name of the wallet provider (e.g., 'nami', 'eternl', etc.)
* @returns Promise resolving to a CIP-30 compliant WalletApi instance
* @throws Error if provider is not supported or wallet is not available
*/
getWalletApi(name: string): Promise<WalletApi>;
}
// Registry of supported wallet providers
const supportedProviders: { [key: string]: boolean } = {
nami: true,
flint: true,
yoroi: true,
gerowallet: true,
eternl: true,
typhoncip30: true,
LodeWallet: true,
exodus: true,
vespr: true,
lace: true,
nufi: true,
};// src/features/Topbar/ConnectWallet/index.tsx
export const ConnectWallet: FC<Props> = ({ fullWidth, className }) => {
const [isOpen, setOpen] = useState(false);
const [isChecked, setIsChecked] = useState(false);
const dispatch = useAppDispatch();
const ws = useContext(WebsocketContext);
const dispatchSelectWallet = (walletName: string) => async () => {
dispatch(setWalletByProvider({ name: walletName, ws }));
};
return (
<div className={className}>
<Button
className={cn(fullWidth && "w-full", "text-sm")}
size="sm"
onClick={() => setOpen(!isOpen)}
>
Connect Wallet
</Button>
<Modal open={isOpen} blur={true} onClose={() => setOpen(false)}>
{/* Terms acceptance and wallet selection UI */}
</Modal>
</div>
);
};// src/store/slices/walletSlice.ts
export const walletSlice = createSlice({
name: "wallet",
initialState,
reducers: {
setWalletFeeAddress: (state, action: PayloadAction<string | null>) => {
state.feeAddress = action.payload;
},
toggleShowWalletSelect: (state) => {
state.showWalletSelect = !state.showWalletSelect;
},
setRewardAccounts: (state, action: PayloadAction<RewardAccount[]>) => {
state.rewardAccounts = action.payload;
},
},
extraReducers: (builder) => {
builder
.addCase(updateWalletUtxosThunk.fulfilled, (state, action) => {
// Handle UTxO updates
})
.addCase(setWalletByProvider.fulfilled, (state, action) => {
state.wallet = action.payload;
state.showWalletSelect = false;
})
.addCase(disconnectWalletThunk.fulfilled, (state, _action) => {
if (state.wallet !== null) {
state.wallet = null;
state.partialWallet.utxos = [];
}
});
},
});// src/websocket.tsx
const WebsocketProvider: FC<{ children: ReactNode }> = ({ children }) => {
const wallet = useAppSelector(selectWallet);
const url = `${wsUrl}`;
const ws = new WebSocket(url);
const dispatch = useAppDispatch();
// Handle successful connection
ws.addEventListener("open", (event) => {
if (wallet !== null) {
sendWalletConnectWsNotif(ws, wallet.address);
}
// Set up heartbeat mechanism
const timer = setInterval(() => {
if (ws.readyState === WebSocket.OPEN) {
ws.send("ping");
}
}, 30000);
});
// Handle connection closure and errors
ws.addEventListener("close", (event) => {
setTimeout(() => setReconnectToggle(!reconnectToggle), 5000);
});
ws.addEventListener("error", (event) => {
setTimeout(() => setReconnectToggle(!reconnectToggle), 5000);
});
// Handle incoming messages
ws.addEventListener("message", (event) => {
const data = event.data;
if (data === "pong") return;
const o = JSON.parse(data);
if (isJsonRpcNotif("RewardDistsView", isRewardAccounts)(o)) {
if (o.params !== undefined) {
dispatch(setRewardAccounts(o.params));
}
}
});
return (
<WebsocketContext.Provider value={ws}>{children}</WebsocketContext.Provider>
);
};// src/utils/wallet-stuff.ts
export type WalletUtxoMap = {
outputUtxosRefByWalletUtxoId: {
[walletUtxoId: string]: string;
};
outputUtxosByOutputUtxosRef: {
[outputUtxosRef: string]: Server.Utxo[];
};
walletUtxoIdsByOutputUtxosRef: {
[outputUtxosRef: string]: Set<string>;
};
};